@playcanvas/web-components 0.13.1 → 0.15.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/dist/pwc.cjs CHANGED
@@ -184,6 +184,7 @@ const REMOVAL_DELAY_MS = 250;
184
184
  * All styling is inline, so the library injects no stylesheet. The colors and height resolve CSS
185
185
  * custom properties — `--pc-loading-bar-color`, `--pc-loading-bar-background` and
186
186
  * `--pc-loading-bar-height` — so a page can theme the bar from `pc-app` or `:root`.
187
+ * @internal
187
188
  */
188
189
  class LoadingBar {
189
190
  _track;
@@ -282,6 +283,11 @@ class LoadingBar {
282
283
  }
283
284
  }
284
285
 
286
+ /**
287
+ * The CSS color keywords, lowercase name to hex value. Read by `parseColor` to accept color
288
+ * names as attribute values.
289
+ * @internal
290
+ */
285
291
  const CSS_COLORS = {
286
292
  aliceblue: '#f0f8ff',
287
293
  antiquewhite: '#faebd7',
@@ -457,7 +463,7 @@ const CSS_COLORS = {
457
463
  * @param value - The value to split.
458
464
  * @param count - The required number of components.
459
465
  * @returns The parsed components, or `null`.
460
- * @ignore
466
+ * @internal
461
467
  */
462
468
  const parseComponents = (value, count) => {
463
469
  const components = value.trim().split(/\s+/).map(Number);
@@ -488,6 +494,7 @@ const cloneDefault = (value) => {
488
494
  * @param value - The attribute value to parse (`null` when the attribute is absent).
489
495
  * @param defaultValue - The value to use when the attribute is absent or removed.
490
496
  * @returns The parsed boolean.
497
+ * @internal
491
498
  */
492
499
  const parseBool = (value, defaultValue) => {
493
500
  return value === null ? defaultValue : value !== 'false';
@@ -503,6 +510,7 @@ const parseBool = (value, defaultValue) => {
503
510
  * @param defaultValue - The value to use when the attribute is absent or invalid.
504
511
  * @param attribute - The attribute name, used in the warning message.
505
512
  * @returns The parsed Color object.
513
+ * @internal
506
514
  */
507
515
  const parseColor = (value, defaultValue, attribute) => {
508
516
  if (value === null) {
@@ -544,6 +552,7 @@ const parseColor = (value, defaultValue, attribute) => {
544
552
  * @param defaultValue - The value to use when the attribute is absent or invalid.
545
553
  * @param attribute - The attribute name, used in the warning message.
546
554
  * @returns The resolved enum name.
555
+ * @internal
547
556
  */
548
557
  const parseEnum = (value, valid, defaultValue, attribute) => {
549
558
  if (value === null) {
@@ -565,6 +574,7 @@ const parseEnum = (value, valid, defaultValue, attribute) => {
565
574
  * @param defaultValue - The value to use when the attribute is absent or invalid.
566
575
  * @param attribute - The attribute name, used in the warning message.
567
576
  * @returns The parsed number.
577
+ * @internal
568
578
  */
569
579
  const parseNumber = (value, defaultValue, attribute) => {
570
580
  if (value === null) {
@@ -587,6 +597,7 @@ const parseNumber = (value, defaultValue, attribute) => {
587
597
  * @param defaultValue - The value to use when the attribute is absent or invalid.
588
598
  * @param attribute - The attribute name, used in the warning message.
589
599
  * @returns The parsed Quat object.
600
+ * @internal
590
601
  */
591
602
  const parseQuat = (value, defaultValue, attribute) => {
592
603
  if (value === null) {
@@ -610,6 +621,7 @@ const parseQuat = (value, defaultValue, attribute) => {
610
621
  * @param value - The attribute value to parse (`null` when the attribute is absent).
611
622
  * @param defaultValue - The value to use when the attribute is absent or removed.
612
623
  * @returns The parsed tag names.
624
+ * @internal
613
625
  */
614
626
  const parseTags = (value, defaultValue = []) => {
615
627
  if (value === null) {
@@ -631,6 +643,7 @@ const parseTags = (value, defaultValue = []) => {
631
643
  * @param defaultValue - The value to use when the attribute is absent or invalid.
632
644
  * @param attribute - The attribute name, used in the warning message.
633
645
  * @returns The parsed Vec2 object.
646
+ * @internal
634
647
  */
635
648
  const parseVec2 = (value, defaultValue, attribute) => {
636
649
  if (value === null) {
@@ -652,6 +665,7 @@ const parseVec2 = (value, defaultValue, attribute) => {
652
665
  * @param defaultValue - The value to use when the attribute is absent or invalid.
653
666
  * @param attribute - The attribute name, used in the warning message.
654
667
  * @returns The parsed Vec3 object.
668
+ * @internal
655
669
  */
656
670
  const parseVec3 = (value, defaultValue, attribute) => {
657
671
  if (value === null) {
@@ -673,6 +687,7 @@ const parseVec3 = (value, defaultValue, attribute) => {
673
687
  * @param defaultValue - The value to use when the attribute is absent or invalid.
674
688
  * @param attribute - The attribute name, used in the warning message.
675
689
  * @returns The parsed Vec4 object.
690
+ * @internal
676
691
  */
677
692
  const parseVec4 = (value, defaultValue, attribute) => {
678
693
  if (value === null) {
@@ -692,6 +707,7 @@ const parseVec4 = (value, defaultValue, attribute) => {
692
707
  *
693
708
  * @param ref - The reference string to resolve.
694
709
  * @returns The resolved entity, or `null`.
710
+ * @internal
695
711
  */
696
712
  const getEntity = (ref) => {
697
713
  if (!ref) {
@@ -861,6 +877,23 @@ class AppElement extends AsyncElement {
861
877
  if (this._loadingBar && !this._bar) {
862
878
  this._bar = new LoadingBar(this);
863
879
  }
880
+ // Upgrade the subtree before reading anything out of it. A subtree cloned from a
881
+ // <template> arrives entirely unupgraded - template content lives in an inert document,
882
+ // where custom element definitions are never looked up - and appending the clone upgrades
883
+ // its elements in tree order, this one before its descendants. The module query below would
884
+ // otherwise find plain HTMLElements with no _getLoadPromise to call, and the boot would die
885
+ // there, leaving the element permanently unready: no canvas, no entities, no application.
886
+ //
887
+ // Upgrading is the fix here rather than skipping whatever has not upgraded, because a
888
+ // <pc-module> is the one child that nothing else ever builds on its own behalf - skipping
889
+ // it would drop the wasm module the app asked for, silently and only for cloned apps.
890
+ // Upgrading runs each descendant's connectedCallback synchronously, a few lines earlier
891
+ // than the parser's path runs them but into the same state they see there: no application
892
+ // yet and _hierarchyReady false, so they defer to the sweeps below. A descendant that
893
+ // disconnects this element from there is caught by the generation check after the await,
894
+ // as any other disconnect is. An already-upgraded subtree - every other insertion path -
895
+ // is left completely untouched.
896
+ customElements.upgrade(this);
864
897
  // Get all pc-module elements that are direct children of the pc-app element
865
898
  const moduleElements = this.querySelectorAll(':scope > pc-module');
866
899
  // Wait for all modules to load
@@ -1596,7 +1629,7 @@ customElements.define('pc-app', AppElement);
1596
1629
  /**
1597
1630
  * The attribute names of the inline `onpointer*` event handlers, shared by every element that
1598
1631
  * fronts an engine entity. Spread into `observedAttributes` by subclasses.
1599
- * @ignore
1632
+ * @internal
1600
1633
  */
1601
1634
  const POINTER_ATTRIBUTES = [
1602
1635
  'onpointerenter',
@@ -1712,6 +1745,30 @@ class EntityBaseElement extends AsyncElement {
1712
1745
  }
1713
1746
  }
1714
1747
 
1748
+ /**
1749
+ * Creates and parents the entities of every descendant `<pc-entity>` of `root`, in two passes so
1750
+ * that no parent's existence depends on document order. Called wherever a subtree could not build
1751
+ * itself: an element inserted into an application that is already running, and a `<pc-node>` whose
1752
+ * children waited for it to bind.
1753
+ *
1754
+ * Descendants that are not yet custom elements are skipped, because there is nothing useful to do
1755
+ * for them and reaching for `_createEntity` would throw. A subtree cloned from a `<template>`
1756
+ * arrives entirely unupgraded — template content lives in an inert document, where custom element
1757
+ * definitions are never looked up — and appending the clone upgrades its elements in tree order,
1758
+ * an element before its descendants. So a sweep from an element's own `connectedCallback` sees
1759
+ * plain `HTMLElement`s below it. Each becomes an `EntityElement` moments later and its own
1760
+ * `connectedCallback` creates and parents it, by which time the ancestor it parents under has its
1761
+ * entity — the same guarantee tree order gives this sweep.
1762
+ *
1763
+ * @param root - The element whose descendant entities to build.
1764
+ * @param app - The application to create the entities in.
1765
+ * @internal
1766
+ */
1767
+ const buildDescendantEntities = (root, app) => {
1768
+ const children = Array.from(root.querySelectorAll('pc-entity')).filter((child) => child instanceof EntityElement);
1769
+ children.forEach((child) => child._createEntity(app));
1770
+ children.forEach((child) => child._buildHierarchy(app));
1771
+ };
1715
1772
  /**
1716
1773
  * The EntityElement interface provides properties and methods for manipulating
1717
1774
  * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-entity/ | `<pc-entity>`} elements.
@@ -1860,13 +1917,7 @@ class EntityElement extends EntityBaseElement {
1860
1917
  this._createEntity(app);
1861
1918
  this._buildHierarchy(app);
1862
1919
  // Handle any child entities that might exist
1863
- const childEntities = this.querySelectorAll('pc-entity');
1864
- childEntities.forEach((child) => {
1865
- child._createEntity(app);
1866
- });
1867
- childEntities.forEach((child) => {
1868
- child._buildHierarchy(app);
1869
- });
1920
+ buildDescendantEntities(this, app);
1870
1921
  }
1871
1922
  }
1872
1923
  disconnectedCallback() {
@@ -4639,219 +4690,1092 @@ class ElementComponentElement extends ComponentElement {
4639
4690
  set width(value) {
4640
4691
  this._width = value;
4641
4692
  if (this.component) {
4642
- this.component.width = value;
4693
+ this.component.width = value;
4694
+ }
4695
+ }
4696
+ /**
4697
+ * Gets the width of the element component.
4698
+ * @returns The width.
4699
+ */
4700
+ get width() {
4701
+ return this._width;
4702
+ }
4703
+ /**
4704
+ * Sets whether the element component should wrap lines.
4705
+ * @param value - Whether to wrap lines.
4706
+ */
4707
+ set wrapLines(value) {
4708
+ this._wrapLines = value;
4709
+ if (this.component) {
4710
+ this.component.wrapLines = value;
4711
+ }
4712
+ }
4713
+ /**
4714
+ * Gets whether the element component should wrap lines.
4715
+ * @returns Whether to wrap lines.
4716
+ */
4717
+ get wrapLines() {
4718
+ return this._wrapLines;
4719
+ }
4720
+ /**
4721
+ * Sets whether a text element should automatically reduce its font size (down to `min-font-size`)
4722
+ * so the text fits within the element's width. Requires `auto-width` to be `false`.
4723
+ * @param value - Whether to auto-fit the width.
4724
+ */
4725
+ set autoFitWidth(value) {
4726
+ this._autoFitWidth = value;
4727
+ if (this.component) {
4728
+ this.component.autoFitWidth = value;
4729
+ }
4730
+ }
4731
+ /**
4732
+ * Gets whether a text element automatically reduces its font size to fit its width.
4733
+ * @returns Whether the width is auto-fit.
4734
+ */
4735
+ get autoFitWidth() {
4736
+ return this._autoFitWidth;
4737
+ }
4738
+ /**
4739
+ * Sets whether a text element should automatically reduce its font size (down to `min-font-size`)
4740
+ * so the text fits within the element's height. Requires `auto-height` to be `false`.
4741
+ * @param value - Whether to auto-fit the height.
4742
+ */
4743
+ set autoFitHeight(value) {
4744
+ this._autoFitHeight = value;
4745
+ if (this.component) {
4746
+ this.component.autoFitHeight = value;
4747
+ }
4748
+ }
4749
+ /**
4750
+ * Gets whether a text element automatically reduces its font size to fit its height.
4751
+ * @returns Whether the height is auto-fit.
4752
+ */
4753
+ get autoFitHeight() {
4754
+ return this._autoFitHeight;
4755
+ }
4756
+ /**
4757
+ * Sets the smallest font size a text element may use when auto-fitting.
4758
+ * @param value - The minimum font size.
4759
+ */
4760
+ set minFontSize(value) {
4761
+ this._minFontSize = value;
4762
+ if (this.component) {
4763
+ this.component.minFontSize = value;
4764
+ }
4765
+ }
4766
+ /**
4767
+ * Gets the smallest font size a text element may use when auto-fitting.
4768
+ * @returns The minimum font size.
4769
+ */
4770
+ get minFontSize() {
4771
+ return this._minFontSize;
4772
+ }
4773
+ /**
4774
+ * Sets the largest font size a text element may use when auto-fitting.
4775
+ * @param value - The maximum font size.
4776
+ */
4777
+ set maxFontSize(value) {
4778
+ this._maxFontSize = value;
4779
+ if (this.component) {
4780
+ this.component.maxFontSize = value;
4781
+ }
4782
+ }
4783
+ /**
4784
+ * Gets the largest font size a text element may use when auto-fitting.
4785
+ * @returns The maximum font size.
4786
+ */
4787
+ get maxFontSize() {
4788
+ return this._maxFontSize;
4789
+ }
4790
+ static get observedAttributes() {
4791
+ return [
4792
+ ...super.observedAttributes,
4793
+ 'anchor',
4794
+ 'auto-width',
4795
+ 'auto-height',
4796
+ 'auto-fit-width',
4797
+ 'auto-fit-height',
4798
+ 'color',
4799
+ 'enable-markup',
4800
+ 'font-asset',
4801
+ 'font-size',
4802
+ 'max-font-size',
4803
+ 'min-font-size',
4804
+ 'height',
4805
+ 'line-height',
4806
+ 'margin',
4807
+ 'mask',
4808
+ 'opacity',
4809
+ 'pivot',
4810
+ 'pixels-per-unit',
4811
+ 'sprite-asset',
4812
+ 'sprite-frame',
4813
+ 'text',
4814
+ 'texture-asset',
4815
+ 'type',
4816
+ 'use-input',
4817
+ 'width',
4818
+ 'wrap-lines'
4819
+ ];
4820
+ }
4821
+ attributeChangedCallback(name, _oldValue, newValue) {
4822
+ super.attributeChangedCallback(name, _oldValue, newValue);
4823
+ switch (name) {
4824
+ case 'anchor':
4825
+ this.anchor = parseVec4(newValue, new playcanvas.Vec4(0.5, 0.5, 0.5, 0.5), name);
4826
+ break;
4827
+ case 'auto-width':
4828
+ this.autoWidth = parseBool(newValue, true);
4829
+ break;
4830
+ case 'auto-height':
4831
+ this.autoHeight = parseBool(newValue, true);
4832
+ break;
4833
+ case 'auto-fit-width':
4834
+ this.autoFitWidth = parseBool(newValue, false);
4835
+ break;
4836
+ case 'auto-fit-height':
4837
+ this.autoFitHeight = parseBool(newValue, false);
4838
+ break;
4839
+ case 'color':
4840
+ this.color = parseColor(newValue, playcanvas.Color.WHITE, name);
4841
+ break;
4842
+ case 'enable-markup':
4843
+ this.enableMarkup = parseBool(newValue, false);
4844
+ break;
4845
+ case 'font-asset':
4846
+ this.fontAsset = newValue ?? '';
4847
+ break;
4848
+ case 'font-size':
4849
+ this.fontSize = parseNumber(newValue, 32, name);
4850
+ break;
4851
+ case 'max-font-size':
4852
+ this.maxFontSize = parseNumber(newValue, 32, name);
4853
+ break;
4854
+ case 'min-font-size':
4855
+ this.minFontSize = parseNumber(newValue, 8, name);
4856
+ break;
4857
+ case 'height':
4858
+ this.height = parseNumber(newValue, 0, name);
4859
+ break;
4860
+ case 'line-height':
4861
+ this.lineHeight = parseNumber(newValue, 32, name);
4862
+ break;
4863
+ case 'margin':
4864
+ this.margin = parseVec4(newValue, null, name);
4865
+ break;
4866
+ case 'mask':
4867
+ this.mask = parseBool(newValue, false);
4868
+ break;
4869
+ case 'opacity':
4870
+ this.opacity = parseNumber(newValue, 1, name);
4871
+ break;
4872
+ case 'pivot':
4873
+ this.pivot = parseVec2(newValue, new playcanvas.Vec2(0.5, 0.5), name);
4874
+ break;
4875
+ case 'pixels-per-unit':
4876
+ this.pixelsPerUnit = parseNumber(newValue, null, name);
4877
+ break;
4878
+ case 'sprite-asset':
4879
+ this.spriteAsset = newValue ?? '';
4880
+ break;
4881
+ case 'sprite-frame':
4882
+ this.spriteFrame = parseNumber(newValue, 0, name);
4883
+ break;
4884
+ case 'text':
4885
+ this.text = newValue ?? '';
4886
+ break;
4887
+ case 'texture-asset':
4888
+ this.textureAsset = newValue ?? '';
4889
+ break;
4890
+ case 'type':
4891
+ this.type = parseEnum(newValue, ['group', 'image', 'text'], 'group', name);
4892
+ break;
4893
+ case 'use-input':
4894
+ this.useInput = parseBool(newValue, false);
4895
+ break;
4896
+ case 'width':
4897
+ this.width = parseNumber(newValue, 0, name);
4898
+ break;
4899
+ case 'wrap-lines':
4900
+ this.wrapLines = parseBool(newValue, false);
4901
+ break;
4902
+ }
4903
+ }
4904
+ }
4905
+ customElements.define('pc-element', ElementComponentElement);
4906
+
4907
+ /**
4908
+ * The JointComponentElement interface provides properties and methods for manipulating
4909
+ * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-joint/ | `<pc-joint>`} elements.
4910
+ * The JointComponentElement interface also inherits the properties and methods of the
4911
+ * {@link HTMLElement} interface.
4912
+ *
4913
+ * The entity holding the joint is not itself constrained. Its world transform defines the joint
4914
+ * frame — the anchor point and axes the constraint operates about — with the local X axis as the
4915
+ * primary axis: a hinge rotates about it, a slider translates along it and a ball joint twists
4916
+ * about it. The constrained bodies are referenced by `entity-a` and `entity-b`, both of which need
4917
+ * a rigid body component; leaving `entity-b` empty constrains `entity-a` to a fixed point in world
4918
+ * space. The underlying engine component is in alpha, so its API may change.
4919
+ *
4920
+ * @fires {CustomEvent} break - Fired when the impulse on the joint exceeds `break-impulse` and the
4921
+ * constraint breaks. A broken joint no longer constrains its bodies; calling `refreshFrames()` on
4922
+ * the underlying component re-attaches it. Bubbles and is composed.
4923
+ *
4924
+ * @category Components
4925
+ */
4926
+ class JointComponentElement extends ComponentElement {
4927
+ /**
4928
+ * The spring damping of the joint per angular axis.
4929
+ */
4930
+ _angularDamping = new playcanvas.Vec3(1, 1, 1);
4931
+ /**
4932
+ * The rest angle of the joint's angular springs.
4933
+ */
4934
+ _angularEquilibrium = new playcanvas.Vec3();
4935
+ /**
4936
+ * The rotation limits of the joint about its X axis.
4937
+ */
4938
+ _angularLimitsX = new playcanvas.Vec2();
4939
+ /**
4940
+ * The rotation limits of the joint about its Y axis.
4941
+ */
4942
+ _angularLimitsY = new playcanvas.Vec2();
4943
+ /**
4944
+ * The rotation limits of the joint about its Z axis.
4945
+ */
4946
+ _angularLimitsZ = new playcanvas.Vec2();
4947
+ /**
4948
+ * The rotational degree of freedom of the joint about its X axis.
4949
+ */
4950
+ _angularMotionX = 'locked';
4951
+ /**
4952
+ * The rotational degree of freedom of the joint about its Y axis.
4953
+ */
4954
+ _angularMotionY = 'locked';
4955
+ /**
4956
+ * The rotational degree of freedom of the joint about its Z axis.
4957
+ */
4958
+ _angularMotionZ = 'locked';
4959
+ /**
4960
+ * The spring stiffness of the joint per angular axis.
4961
+ */
4962
+ _angularStiffness = new playcanvas.Vec3();
4963
+ /**
4964
+ * The impulse above which the joint breaks.
4965
+ */
4966
+ _breakImpulse = Infinity;
4967
+ /**
4968
+ * Whether collision is enabled between the constrained bodies.
4969
+ */
4970
+ _enableCollision = false;
4971
+ /**
4972
+ * Whether the joint's limits are enforced.
4973
+ */
4974
+ _enableLimits = false;
4975
+ /**
4976
+ * The reference to the entity providing the first constrained body.
4977
+ */
4978
+ _entityA = '';
4979
+ /**
4980
+ * The reference to the entity providing the second constrained body.
4981
+ */
4982
+ _entityB = '';
4983
+ /**
4984
+ * The rotation or travel limits of the joint.
4985
+ */
4986
+ _limits = new playcanvas.Vec2(-45, 45);
4987
+ /**
4988
+ * The spring damping of the joint per linear axis.
4989
+ */
4990
+ _linearDamping = new playcanvas.Vec3(1, 1, 1);
4991
+ /**
4992
+ * The rest point of the joint's linear springs.
4993
+ */
4994
+ _linearEquilibrium = new playcanvas.Vec3();
4995
+ /**
4996
+ * The translation limits of the joint along its X axis.
4997
+ */
4998
+ _linearLimitsX = new playcanvas.Vec2();
4999
+ /**
5000
+ * The translation limits of the joint along its Y axis.
5001
+ */
5002
+ _linearLimitsY = new playcanvas.Vec2();
5003
+ /**
5004
+ * The translation limits of the joint along its Z axis.
5005
+ */
5006
+ _linearLimitsZ = new playcanvas.Vec2();
5007
+ /**
5008
+ * The linear degree of freedom of the joint along its X axis.
5009
+ */
5010
+ _linearMotionX = 'locked';
5011
+ /**
5012
+ * The linear degree of freedom of the joint along its Y axis.
5013
+ */
5014
+ _linearMotionY = 'locked';
5015
+ /**
5016
+ * The linear degree of freedom of the joint along its Z axis.
5017
+ */
5018
+ _linearMotionZ = 'locked';
5019
+ /**
5020
+ * The spring stiffness of the joint per linear axis.
5021
+ */
5022
+ _linearStiffness = new playcanvas.Vec3();
5023
+ /**
5024
+ * The maximum torque or force of the joint's motor.
5025
+ */
5026
+ _maxMotorForce = 0;
5027
+ /**
5028
+ * The target speed of the joint's motor.
5029
+ */
5030
+ _motorSpeed = 0;
5031
+ /**
5032
+ * The maximum swing of the joint around the joint frame's Y axis.
5033
+ */
5034
+ _swingLimitY = 45;
5035
+ /**
5036
+ * The maximum swing of the joint around the joint frame's Z axis.
5037
+ */
5038
+ _swingLimitZ = 45;
5039
+ /**
5040
+ * The maximum twist of the joint about its primary axis.
5041
+ */
5042
+ _twistLimit = 20;
5043
+ /**
5044
+ * The type of the joint.
5045
+ */
5046
+ _type = 'fixed';
5047
+ /** @ignore */
5048
+ constructor() {
5049
+ super('joint');
5050
+ }
5051
+ getInitialComponentData() {
5052
+ return {
5053
+ angularDamping: this._angularDamping,
5054
+ angularEquilibrium: this._angularEquilibrium,
5055
+ angularLimitsX: this._angularLimitsX,
5056
+ angularLimitsY: this._angularLimitsY,
5057
+ angularLimitsZ: this._angularLimitsZ,
5058
+ angularMotionX: this._angularMotionX,
5059
+ angularMotionY: this._angularMotionY,
5060
+ angularMotionZ: this._angularMotionZ,
5061
+ angularStiffness: this._angularStiffness,
5062
+ breakImpulse: this._breakImpulse,
5063
+ enableCollision: this._enableCollision,
5064
+ enableLimits: this._enableLimits,
5065
+ entityA: getEntity(this._entityA),
5066
+ entityB: getEntity(this._entityB),
5067
+ limits: this._limits,
5068
+ linearDamping: this._linearDamping,
5069
+ linearEquilibrium: this._linearEquilibrium,
5070
+ linearLimitsX: this._linearLimitsX,
5071
+ linearLimitsY: this._linearLimitsY,
5072
+ linearLimitsZ: this._linearLimitsZ,
5073
+ linearMotionX: this._linearMotionX,
5074
+ linearMotionY: this._linearMotionY,
5075
+ linearMotionZ: this._linearMotionZ,
5076
+ linearStiffness: this._linearStiffness,
5077
+ maxMotorForce: this._maxMotorForce,
5078
+ motorSpeed: this._motorSpeed,
5079
+ swingLimitY: this._swingLimitY,
5080
+ swingLimitZ: this._swingLimitZ,
5081
+ twistLimit: this._twistLimit,
5082
+ type: this._type
5083
+ };
5084
+ }
5085
+ _onBreak() {
5086
+ this.dispatchEvent(new CustomEvent('break', { bubbles: true, composed: true }));
5087
+ }
5088
+ initComponent() {
5089
+ const component = this.component;
5090
+ if (!component) {
5091
+ return;
5092
+ }
5093
+ // A host readiness cycle can re-run this against the same surviving component instance,
5094
+ // so the off/on pair keeps the subscription single either way. Component removal destroys
5095
+ // the instance and its listeners with it, so there is no disconnect-side teardown.
5096
+ component.off('break', this._onBreak, this);
5097
+ component.on('break', this._onBreak, this);
5098
+ }
5099
+ /**
5100
+ * Gets the underlying PlayCanvas joint component.
5101
+ * @returns The joint component.
5102
+ */
5103
+ get component() {
5104
+ return super.component;
5105
+ }
5106
+ /**
5107
+ * Sets the spring damping of a 6dof joint per angular axis, used on axes with a non-zero
5108
+ * angular-stiffness.
5109
+ * @param value - The angular spring damping.
5110
+ */
5111
+ set angularDamping(value) {
5112
+ this._angularDamping = value;
5113
+ if (this.component) {
5114
+ this.component.angularDamping = value;
5115
+ }
5116
+ }
5117
+ /**
5118
+ * Gets the spring damping of the joint per angular axis.
5119
+ * @returns The angular spring damping.
5120
+ */
5121
+ get angularDamping() {
5122
+ return this._angularDamping;
5123
+ }
5124
+ /**
5125
+ * Sets the rest angle of a 6dof joint's angular springs in degrees per axis, used on axes with
5126
+ * a non-zero angular-stiffness.
5127
+ * @param value - The angular spring rest angles.
5128
+ */
5129
+ set angularEquilibrium(value) {
5130
+ this._angularEquilibrium = value;
5131
+ if (this.component) {
5132
+ this.component.angularEquilibrium = value;
5133
+ }
5134
+ }
5135
+ /**
5136
+ * Gets the rest angle of the joint's angular springs.
5137
+ * @returns The angular spring rest angles.
5138
+ */
5139
+ get angularEquilibrium() {
5140
+ return this._angularEquilibrium;
5141
+ }
5142
+ /**
5143
+ * Sets the lower and upper rotation limit of a 6dof joint about its X axis in degrees, used
5144
+ * when angular-motion-x is limited.
5145
+ * @param value - The X axis rotation limits.
5146
+ */
5147
+ set angularLimitsX(value) {
5148
+ this._angularLimitsX = value;
5149
+ if (this.component) {
5150
+ this.component.angularLimitsX = value;
5151
+ }
5152
+ }
5153
+ /**
5154
+ * Gets the rotation limits of the joint about its X axis.
5155
+ * @returns The X axis rotation limits.
5156
+ */
5157
+ get angularLimitsX() {
5158
+ return this._angularLimitsX;
5159
+ }
5160
+ /**
5161
+ * Sets the lower and upper rotation limit of a 6dof joint about its Y axis in degrees, used
5162
+ * when angular-motion-y is limited.
5163
+ * @param value - The Y axis rotation limits.
5164
+ */
5165
+ set angularLimitsY(value) {
5166
+ this._angularLimitsY = value;
5167
+ if (this.component) {
5168
+ this.component.angularLimitsY = value;
5169
+ }
5170
+ }
5171
+ /**
5172
+ * Gets the rotation limits of the joint about its Y axis.
5173
+ * @returns The Y axis rotation limits.
5174
+ */
5175
+ get angularLimitsY() {
5176
+ return this._angularLimitsY;
5177
+ }
5178
+ /**
5179
+ * Sets the lower and upper rotation limit of a 6dof joint about its Z axis in degrees, used
5180
+ * when angular-motion-z is limited.
5181
+ * @param value - The Z axis rotation limits.
5182
+ */
5183
+ set angularLimitsZ(value) {
5184
+ this._angularLimitsZ = value;
5185
+ if (this.component) {
5186
+ this.component.angularLimitsZ = value;
5187
+ }
5188
+ }
5189
+ /**
5190
+ * Gets the rotation limits of the joint about its Z axis.
5191
+ * @returns The Z axis rotation limits.
5192
+ */
5193
+ get angularLimitsZ() {
5194
+ return this._angularLimitsZ;
5195
+ }
5196
+ /**
5197
+ * Sets how a 6dof joint constrains rotation about its X axis. Can be `locked`, `limited` or
5198
+ * `free`. Defaults to `locked`.
5199
+ * @param value - The X axis rotational degree of freedom.
5200
+ */
5201
+ set angularMotionX(value) {
5202
+ this._angularMotionX = value;
5203
+ if (this.component) {
5204
+ this.component.angularMotionX = value;
5205
+ }
5206
+ }
5207
+ /**
5208
+ * Gets how the joint constrains rotation about its X axis.
5209
+ * @returns The X axis rotational degree of freedom.
5210
+ */
5211
+ get angularMotionX() {
5212
+ return this._angularMotionX;
5213
+ }
5214
+ /**
5215
+ * Sets how a 6dof joint constrains rotation about its Y axis. Can be `locked`, `limited` or
5216
+ * `free`. Defaults to `locked`.
5217
+ * @param value - The Y axis rotational degree of freedom.
5218
+ */
5219
+ set angularMotionY(value) {
5220
+ this._angularMotionY = value;
5221
+ if (this.component) {
5222
+ this.component.angularMotionY = value;
5223
+ }
5224
+ }
5225
+ /**
5226
+ * Gets how the joint constrains rotation about its Y axis.
5227
+ * @returns The Y axis rotational degree of freedom.
5228
+ */
5229
+ get angularMotionY() {
5230
+ return this._angularMotionY;
5231
+ }
5232
+ /**
5233
+ * Sets how a 6dof joint constrains rotation about its Z axis. Can be `locked`, `limited` or
5234
+ * `free`. Defaults to `locked`.
5235
+ * @param value - The Z axis rotational degree of freedom.
5236
+ */
5237
+ set angularMotionZ(value) {
5238
+ this._angularMotionZ = value;
5239
+ if (this.component) {
5240
+ this.component.angularMotionZ = value;
5241
+ }
5242
+ }
5243
+ /**
5244
+ * Gets how the joint constrains rotation about its Z axis.
5245
+ * @returns The Z axis rotational degree of freedom.
5246
+ */
5247
+ get angularMotionZ() {
5248
+ return this._angularMotionZ;
5249
+ }
5250
+ /**
5251
+ * Sets the spring stiffness of a 6dof joint per angular axis, where 0 disables the spring on
5252
+ * that axis.
5253
+ * @param value - The angular spring stiffness.
5254
+ */
5255
+ set angularStiffness(value) {
5256
+ this._angularStiffness = value;
5257
+ if (this.component) {
5258
+ this.component.angularStiffness = value;
5259
+ }
5260
+ }
5261
+ /**
5262
+ * Gets the spring stiffness of the joint per angular axis.
5263
+ * @returns The angular spring stiffness.
5264
+ */
5265
+ get angularStiffness() {
5266
+ return this._angularStiffness;
5267
+ }
5268
+ /**
5269
+ * Sets the impulse in newton seconds above which the joint breaks. Defaults to `Infinity`,
5270
+ * which makes the joint unbreakable.
5271
+ * @param value - The break impulse.
5272
+ */
5273
+ set breakImpulse(value) {
5274
+ this._breakImpulse = value;
5275
+ if (this.component) {
5276
+ this.component.breakImpulse = value;
5277
+ }
5278
+ }
5279
+ /**
5280
+ * Gets the impulse above which the joint breaks.
5281
+ * @returns The break impulse.
5282
+ */
5283
+ get breakImpulse() {
5284
+ return this._breakImpulse;
5285
+ }
5286
+ /**
5287
+ * Sets whether collision is enabled between the two constrained bodies.
5288
+ * @param value - Whether collision is enabled.
5289
+ */
5290
+ set enableCollision(value) {
5291
+ this._enableCollision = value;
5292
+ if (this.component) {
5293
+ this.component.enableCollision = value;
5294
+ }
5295
+ }
5296
+ /**
5297
+ * Gets whether collision is enabled between the two constrained bodies.
5298
+ * @returns Whether collision is enabled.
5299
+ */
5300
+ get enableCollision() {
5301
+ return this._enableCollision;
5302
+ }
5303
+ /**
5304
+ * Sets whether the limits of a hinge, slider or ball joint are enforced.
5305
+ * @param value - Whether the limits are enforced.
5306
+ */
5307
+ set enableLimits(value) {
5308
+ this._enableLimits = value;
5309
+ if (this.component) {
5310
+ this.component.enableLimits = value;
5311
+ }
5312
+ }
5313
+ /**
5314
+ * Gets whether the limits of the joint are enforced.
5315
+ * @returns Whether the limits are enforced.
5316
+ */
5317
+ get enableLimits() {
5318
+ return this._enableLimits;
5319
+ }
5320
+ /**
5321
+ * Sets the reference (CSS selector, element id or entity name) to the `<pc-entity>` providing
5322
+ * the first constrained body. The reference resolves when it is set, so an entity created
5323
+ * later is picked up by setting the attribute again.
5324
+ * @param value - The first body's entity reference.
5325
+ */
5326
+ set entityA(value) {
5327
+ this._entityA = value;
5328
+ if (this.component) {
5329
+ this.component.entityA = getEntity(value);
5330
+ }
5331
+ }
5332
+ /**
5333
+ * Gets the reference to the `<pc-entity>` providing the first constrained body.
5334
+ * @returns The first body's entity reference.
5335
+ */
5336
+ get entityA() {
5337
+ return this._entityA;
5338
+ }
5339
+ /**
5340
+ * Sets the reference (CSS selector, element id or entity name) to the `<pc-entity>` providing
5341
+ * the second constrained body, or empty to constrain the first body to a fixed point in world
5342
+ * space. The reference resolves when it is set, so an entity created later is picked up by
5343
+ * setting the attribute again.
5344
+ * @param value - The second body's entity reference.
5345
+ */
5346
+ set entityB(value) {
5347
+ this._entityB = value;
5348
+ if (this.component) {
5349
+ this.component.entityB = getEntity(value);
5350
+ }
5351
+ }
5352
+ /**
5353
+ * Gets the reference to the `<pc-entity>` providing the second constrained body.
5354
+ * @returns The second body's entity reference.
5355
+ */
5356
+ get entityB() {
5357
+ return this._entityB;
5358
+ }
5359
+ /**
5360
+ * Sets the lower and upper limit of a hinge joint's rotation in degrees, or a slider joint's
5361
+ * travel in meters, applied when enable-limits is set.
5362
+ * @param value - The rotation or travel limits.
5363
+ */
5364
+ set limits(value) {
5365
+ this._limits = value;
5366
+ if (this.component) {
5367
+ this.component.limits = value;
5368
+ }
5369
+ }
5370
+ /**
5371
+ * Gets the rotation or travel limits of the joint.
5372
+ * @returns The rotation or travel limits.
5373
+ */
5374
+ get limits() {
5375
+ return this._limits;
5376
+ }
5377
+ /**
5378
+ * Sets the spring damping of a 6dof joint per linear axis, used on axes with a non-zero
5379
+ * linear-stiffness.
5380
+ * @param value - The linear spring damping.
5381
+ */
5382
+ set linearDamping(value) {
5383
+ this._linearDamping = value;
5384
+ if (this.component) {
5385
+ this.component.linearDamping = value;
5386
+ }
5387
+ }
5388
+ /**
5389
+ * Gets the spring damping of the joint per linear axis.
5390
+ * @returns The linear spring damping.
5391
+ */
5392
+ get linearDamping() {
5393
+ return this._linearDamping;
5394
+ }
5395
+ /**
5396
+ * Sets the rest point of a 6dof joint's linear springs in meters per axis, used on axes with a
5397
+ * non-zero linear-stiffness.
5398
+ * @param value - The linear spring rest points.
5399
+ */
5400
+ set linearEquilibrium(value) {
5401
+ this._linearEquilibrium = value;
5402
+ if (this.component) {
5403
+ this.component.linearEquilibrium = value;
5404
+ }
5405
+ }
5406
+ /**
5407
+ * Gets the rest point of the joint's linear springs.
5408
+ * @returns The linear spring rest points.
5409
+ */
5410
+ get linearEquilibrium() {
5411
+ return this._linearEquilibrium;
5412
+ }
5413
+ /**
5414
+ * Sets the lower and upper translation limit of a 6dof joint along its X axis in meters, used
5415
+ * when linear-motion-x is limited.
5416
+ * @param value - The X axis translation limits.
5417
+ */
5418
+ set linearLimitsX(value) {
5419
+ this._linearLimitsX = value;
5420
+ if (this.component) {
5421
+ this.component.linearLimitsX = value;
5422
+ }
5423
+ }
5424
+ /**
5425
+ * Gets the translation limits of the joint along its X axis.
5426
+ * @returns The X axis translation limits.
5427
+ */
5428
+ get linearLimitsX() {
5429
+ return this._linearLimitsX;
5430
+ }
5431
+ /**
5432
+ * Sets the lower and upper translation limit of a 6dof joint along its Y axis in meters, used
5433
+ * when linear-motion-y is limited.
5434
+ * @param value - The Y axis translation limits.
5435
+ */
5436
+ set linearLimitsY(value) {
5437
+ this._linearLimitsY = value;
5438
+ if (this.component) {
5439
+ this.component.linearLimitsY = value;
5440
+ }
5441
+ }
5442
+ /**
5443
+ * Gets the translation limits of the joint along its Y axis.
5444
+ * @returns The Y axis translation limits.
5445
+ */
5446
+ get linearLimitsY() {
5447
+ return this._linearLimitsY;
5448
+ }
5449
+ /**
5450
+ * Sets the lower and upper translation limit of a 6dof joint along its Z axis in meters, used
5451
+ * when linear-motion-z is limited.
5452
+ * @param value - The Z axis translation limits.
5453
+ */
5454
+ set linearLimitsZ(value) {
5455
+ this._linearLimitsZ = value;
5456
+ if (this.component) {
5457
+ this.component.linearLimitsZ = value;
5458
+ }
5459
+ }
5460
+ /**
5461
+ * Gets the translation limits of the joint along its Z axis.
5462
+ * @returns The Z axis translation limits.
5463
+ */
5464
+ get linearLimitsZ() {
5465
+ return this._linearLimitsZ;
5466
+ }
5467
+ /**
5468
+ * Sets how a 6dof joint constrains translation along its X axis. Can be `locked`, `limited` or
5469
+ * `free`. Defaults to `locked`.
5470
+ * @param value - The X axis linear degree of freedom.
5471
+ */
5472
+ set linearMotionX(value) {
5473
+ this._linearMotionX = value;
5474
+ if (this.component) {
5475
+ this.component.linearMotionX = value;
5476
+ }
5477
+ }
5478
+ /**
5479
+ * Gets how the joint constrains translation along its X axis.
5480
+ * @returns The X axis linear degree of freedom.
5481
+ */
5482
+ get linearMotionX() {
5483
+ return this._linearMotionX;
5484
+ }
5485
+ /**
5486
+ * Sets how a 6dof joint constrains translation along its Y axis. Can be `locked`, `limited` or
5487
+ * `free`. Defaults to `locked`.
5488
+ * @param value - The Y axis linear degree of freedom.
5489
+ */
5490
+ set linearMotionY(value) {
5491
+ this._linearMotionY = value;
5492
+ if (this.component) {
5493
+ this.component.linearMotionY = value;
5494
+ }
5495
+ }
5496
+ /**
5497
+ * Gets how the joint constrains translation along its Y axis.
5498
+ * @returns The Y axis linear degree of freedom.
5499
+ */
5500
+ get linearMotionY() {
5501
+ return this._linearMotionY;
5502
+ }
5503
+ /**
5504
+ * Sets how a 6dof joint constrains translation along its Z axis. Can be `locked`, `limited` or
5505
+ * `free`. Defaults to `locked`.
5506
+ * @param value - The Z axis linear degree of freedom.
5507
+ */
5508
+ set linearMotionZ(value) {
5509
+ this._linearMotionZ = value;
5510
+ if (this.component) {
5511
+ this.component.linearMotionZ = value;
5512
+ }
5513
+ }
5514
+ /**
5515
+ * Gets how the joint constrains translation along its Z axis.
5516
+ * @returns The Z axis linear degree of freedom.
5517
+ */
5518
+ get linearMotionZ() {
5519
+ return this._linearMotionZ;
5520
+ }
5521
+ /**
5522
+ * Sets the spring stiffness of a 6dof joint per linear axis, where 0 disables the spring on
5523
+ * that axis.
5524
+ * @param value - The linear spring stiffness.
5525
+ */
5526
+ set linearStiffness(value) {
5527
+ this._linearStiffness = value;
5528
+ if (this.component) {
5529
+ this.component.linearStiffness = value;
5530
+ }
5531
+ }
5532
+ /**
5533
+ * Gets the spring stiffness of the joint per linear axis.
5534
+ * @returns The linear spring stiffness.
5535
+ */
5536
+ get linearStiffness() {
5537
+ return this._linearStiffness;
5538
+ }
5539
+ /**
5540
+ * Sets the maximum torque in newton meters of a hinge joint's motor, or the maximum force in
5541
+ * newtons of a slider joint's motor, where 0 disables the motor.
5542
+ * @param value - The maximum motor torque or force.
5543
+ */
5544
+ set maxMotorForce(value) {
5545
+ this._maxMotorForce = value;
5546
+ if (this.component) {
5547
+ this.component.maxMotorForce = value;
4643
5548
  }
4644
5549
  }
4645
5550
  /**
4646
- * Gets the width of the element component.
4647
- * @returns The width.
5551
+ * Gets the maximum torque or force of the joint's motor.
5552
+ * @returns The maximum motor torque or force.
4648
5553
  */
4649
- get width() {
4650
- return this._width;
5554
+ get maxMotorForce() {
5555
+ return this._maxMotorForce;
4651
5556
  }
4652
5557
  /**
4653
- * Sets whether the element component should wrap lines.
4654
- * @param value - Whether to wrap lines.
5558
+ * Sets the target speed of a hinge joint's motor in degrees per second, or a slider joint's
5559
+ * motor in meters per second, active while max-motor-force is greater than 0.
5560
+ * @param value - The motor's target speed.
4655
5561
  */
4656
- set wrapLines(value) {
4657
- this._wrapLines = value;
5562
+ set motorSpeed(value) {
5563
+ this._motorSpeed = value;
4658
5564
  if (this.component) {
4659
- this.component.wrapLines = value;
5565
+ this.component.motorSpeed = value;
4660
5566
  }
4661
5567
  }
4662
5568
  /**
4663
- * Gets whether the element component should wrap lines.
4664
- * @returns Whether to wrap lines.
5569
+ * Gets the target speed of the joint's motor.
5570
+ * @returns The motor's target speed.
4665
5571
  */
4666
- get wrapLines() {
4667
- return this._wrapLines;
5572
+ get motorSpeed() {
5573
+ return this._motorSpeed;
4668
5574
  }
4669
5575
  /**
4670
- * Sets whether a text element should automatically reduce its font size (down to `min-font-size`)
4671
- * so the text fits within the element's width. Requires `auto-width` to be `false`.
4672
- * @param value - Whether to auto-fit the width.
5576
+ * Sets the maximum swing of a ball joint around the joint frame's Y axis in degrees, applied
5577
+ * when enable-limits is set.
5578
+ * @param value - The Y axis swing limit.
4673
5579
  */
4674
- set autoFitWidth(value) {
4675
- this._autoFitWidth = value;
5580
+ set swingLimitY(value) {
5581
+ this._swingLimitY = value;
4676
5582
  if (this.component) {
4677
- this.component.autoFitWidth = value;
5583
+ this.component.swingLimitY = value;
4678
5584
  }
4679
5585
  }
4680
5586
  /**
4681
- * Gets whether a text element automatically reduces its font size to fit its width.
4682
- * @returns Whether the width is auto-fit.
5587
+ * Gets the maximum swing of the joint around the joint frame's Y axis.
5588
+ * @returns The Y axis swing limit.
4683
5589
  */
4684
- get autoFitWidth() {
4685
- return this._autoFitWidth;
5590
+ get swingLimitY() {
5591
+ return this._swingLimitY;
4686
5592
  }
4687
5593
  /**
4688
- * Sets whether a text element should automatically reduce its font size (down to `min-font-size`)
4689
- * so the text fits within the element's height. Requires `auto-height` to be `false`.
4690
- * @param value - Whether to auto-fit the height.
5594
+ * Sets the maximum swing of a ball joint around the joint frame's Z axis in degrees, applied
5595
+ * when enable-limits is set.
5596
+ * @param value - The Z axis swing limit.
4691
5597
  */
4692
- set autoFitHeight(value) {
4693
- this._autoFitHeight = value;
5598
+ set swingLimitZ(value) {
5599
+ this._swingLimitZ = value;
4694
5600
  if (this.component) {
4695
- this.component.autoFitHeight = value;
5601
+ this.component.swingLimitZ = value;
4696
5602
  }
4697
5603
  }
4698
5604
  /**
4699
- * Gets whether a text element automatically reduces its font size to fit its height.
4700
- * @returns Whether the height is auto-fit.
5605
+ * Gets the maximum swing of the joint around the joint frame's Z axis.
5606
+ * @returns The Z axis swing limit.
4701
5607
  */
4702
- get autoFitHeight() {
4703
- return this._autoFitHeight;
5608
+ get swingLimitZ() {
5609
+ return this._swingLimitZ;
4704
5610
  }
4705
5611
  /**
4706
- * Sets the smallest font size a text element may use when auto-fitting.
4707
- * @param value - The minimum font size.
5612
+ * Sets the maximum twist of a ball joint about its primary axis in degrees, applied when
5613
+ * enable-limits is set.
5614
+ * @param value - The twist limit.
4708
5615
  */
4709
- set minFontSize(value) {
4710
- this._minFontSize = value;
5616
+ set twistLimit(value) {
5617
+ this._twistLimit = value;
4711
5618
  if (this.component) {
4712
- this.component.minFontSize = value;
5619
+ this.component.twistLimit = value;
4713
5620
  }
4714
5621
  }
4715
5622
  /**
4716
- * Gets the smallest font size a text element may use when auto-fitting.
4717
- * @returns The minimum font size.
5623
+ * Gets the maximum twist of the joint about its primary axis.
5624
+ * @returns The twist limit.
4718
5625
  */
4719
- get minFontSize() {
4720
- return this._minFontSize;
5626
+ get twistLimit() {
5627
+ return this._twistLimit;
4721
5628
  }
4722
5629
  /**
4723
- * Sets the largest font size a text element may use when auto-fitting.
4724
- * @param value - The maximum font size.
5630
+ * Sets the type of the joint. Can be `fixed`, `ball`, `hinge`, `slider` or `6dof`. Defaults to
5631
+ * `fixed`.
5632
+ * @param value - The joint type.
4725
5633
  */
4726
- set maxFontSize(value) {
4727
- this._maxFontSize = value;
5634
+ set type(value) {
5635
+ this._type = value;
4728
5636
  if (this.component) {
4729
- this.component.maxFontSize = value;
5637
+ this.component.type = value;
4730
5638
  }
4731
5639
  }
4732
5640
  /**
4733
- * Gets the largest font size a text element may use when auto-fitting.
4734
- * @returns The maximum font size.
5641
+ * Gets the type of the joint.
5642
+ * @returns The joint type.
4735
5643
  */
4736
- get maxFontSize() {
4737
- return this._maxFontSize;
5644
+ get type() {
5645
+ return this._type;
4738
5646
  }
4739
5647
  static get observedAttributes() {
4740
5648
  return [
4741
5649
  ...super.observedAttributes,
4742
- 'anchor',
4743
- 'auto-width',
4744
- 'auto-height',
4745
- 'auto-fit-width',
4746
- 'auto-fit-height',
4747
- 'color',
4748
- 'enable-markup',
4749
- 'font-asset',
4750
- 'font-size',
4751
- 'max-font-size',
4752
- 'min-font-size',
4753
- 'height',
4754
- 'line-height',
4755
- 'margin',
4756
- 'mask',
4757
- 'opacity',
4758
- 'pivot',
4759
- 'pixels-per-unit',
4760
- 'sprite-asset',
4761
- 'sprite-frame',
4762
- 'text',
4763
- 'texture-asset',
4764
- 'type',
4765
- 'use-input',
4766
- 'width',
4767
- 'wrap-lines'
5650
+ 'angular-damping',
5651
+ 'angular-equilibrium',
5652
+ 'angular-limits-x',
5653
+ 'angular-limits-y',
5654
+ 'angular-limits-z',
5655
+ 'angular-motion-x',
5656
+ 'angular-motion-y',
5657
+ 'angular-motion-z',
5658
+ 'angular-stiffness',
5659
+ 'break-impulse',
5660
+ 'enable-collision',
5661
+ 'enable-limits',
5662
+ 'entity-a',
5663
+ 'entity-b',
5664
+ 'limits',
5665
+ 'linear-damping',
5666
+ 'linear-equilibrium',
5667
+ 'linear-limits-x',
5668
+ 'linear-limits-y',
5669
+ 'linear-limits-z',
5670
+ 'linear-motion-x',
5671
+ 'linear-motion-y',
5672
+ 'linear-motion-z',
5673
+ 'linear-stiffness',
5674
+ 'max-motor-force',
5675
+ 'motor-speed',
5676
+ 'swing-limit-y',
5677
+ 'swing-limit-z',
5678
+ 'twist-limit',
5679
+ 'type'
4768
5680
  ];
4769
5681
  }
4770
5682
  attributeChangedCallback(name, _oldValue, newValue) {
4771
5683
  super.attributeChangedCallback(name, _oldValue, newValue);
4772
5684
  switch (name) {
4773
- case 'anchor':
4774
- this.anchor = parseVec4(newValue, new playcanvas.Vec4(0.5, 0.5, 0.5, 0.5), name);
5685
+ case 'angular-damping':
5686
+ this.angularDamping = parseVec3(newValue, playcanvas.Vec3.ONE, name);
4775
5687
  break;
4776
- case 'auto-width':
4777
- this.autoWidth = parseBool(newValue, true);
5688
+ case 'angular-equilibrium':
5689
+ this.angularEquilibrium = parseVec3(newValue, playcanvas.Vec3.ZERO, name);
4778
5690
  break;
4779
- case 'auto-height':
4780
- this.autoHeight = parseBool(newValue, true);
5691
+ case 'angular-limits-x':
5692
+ this.angularLimitsX = parseVec2(newValue, playcanvas.Vec2.ZERO, name);
4781
5693
  break;
4782
- case 'auto-fit-width':
4783
- this.autoFitWidth = parseBool(newValue, false);
5694
+ case 'angular-limits-y':
5695
+ this.angularLimitsY = parseVec2(newValue, playcanvas.Vec2.ZERO, name);
4784
5696
  break;
4785
- case 'auto-fit-height':
4786
- this.autoFitHeight = parseBool(newValue, false);
5697
+ case 'angular-limits-z':
5698
+ this.angularLimitsZ = parseVec2(newValue, playcanvas.Vec2.ZERO, name);
4787
5699
  break;
4788
- case 'color':
4789
- this.color = parseColor(newValue, playcanvas.Color.WHITE, name);
5700
+ case 'angular-motion-x':
5701
+ this.angularMotionX = parseEnum(newValue, ['locked', 'limited', 'free'], 'locked', name);
4790
5702
  break;
4791
- case 'enable-markup':
4792
- this.enableMarkup = parseBool(newValue, false);
5703
+ case 'angular-motion-y':
5704
+ this.angularMotionY = parseEnum(newValue, ['locked', 'limited', 'free'], 'locked', name);
4793
5705
  break;
4794
- case 'font-asset':
4795
- this.fontAsset = newValue ?? '';
5706
+ case 'angular-motion-z':
5707
+ this.angularMotionZ = parseEnum(newValue, ['locked', 'limited', 'free'], 'locked', name);
4796
5708
  break;
4797
- case 'font-size':
4798
- this.fontSize = parseNumber(newValue, 32, name);
5709
+ case 'angular-stiffness':
5710
+ this.angularStiffness = parseVec3(newValue, playcanvas.Vec3.ZERO, name);
4799
5711
  break;
4800
- case 'max-font-size':
4801
- this.maxFontSize = parseNumber(newValue, 32, name);
5712
+ case 'break-impulse':
5713
+ this.breakImpulse = parseNumber(newValue, Infinity, name);
4802
5714
  break;
4803
- case 'min-font-size':
4804
- this.minFontSize = parseNumber(newValue, 8, name);
5715
+ case 'enable-collision':
5716
+ this.enableCollision = parseBool(newValue, false);
4805
5717
  break;
4806
- case 'height':
4807
- this.height = parseNumber(newValue, 0, name);
5718
+ case 'enable-limits':
5719
+ this.enableLimits = parseBool(newValue, false);
4808
5720
  break;
4809
- case 'line-height':
4810
- this.lineHeight = parseNumber(newValue, 32, name);
5721
+ case 'entity-a':
5722
+ this.entityA = newValue ?? '';
4811
5723
  break;
4812
- case 'margin':
4813
- this.margin = parseVec4(newValue, null, name);
5724
+ case 'entity-b':
5725
+ this.entityB = newValue ?? '';
4814
5726
  break;
4815
- case 'mask':
4816
- this.mask = parseBool(newValue, false);
5727
+ case 'limits':
5728
+ this.limits = parseVec2(newValue, new playcanvas.Vec2(-45, 45), name);
4817
5729
  break;
4818
- case 'opacity':
4819
- this.opacity = parseNumber(newValue, 1, name);
5730
+ case 'linear-damping':
5731
+ this.linearDamping = parseVec3(newValue, playcanvas.Vec3.ONE, name);
4820
5732
  break;
4821
- case 'pivot':
4822
- this.pivot = parseVec2(newValue, new playcanvas.Vec2(0.5, 0.5), name);
5733
+ case 'linear-equilibrium':
5734
+ this.linearEquilibrium = parseVec3(newValue, playcanvas.Vec3.ZERO, name);
4823
5735
  break;
4824
- case 'pixels-per-unit':
4825
- this.pixelsPerUnit = parseNumber(newValue, null, name);
5736
+ case 'linear-limits-x':
5737
+ this.linearLimitsX = parseVec2(newValue, playcanvas.Vec2.ZERO, name);
4826
5738
  break;
4827
- case 'sprite-asset':
4828
- this.spriteAsset = newValue ?? '';
5739
+ case 'linear-limits-y':
5740
+ this.linearLimitsY = parseVec2(newValue, playcanvas.Vec2.ZERO, name);
4829
5741
  break;
4830
- case 'sprite-frame':
4831
- this.spriteFrame = parseNumber(newValue, 0, name);
5742
+ case 'linear-limits-z':
5743
+ this.linearLimitsZ = parseVec2(newValue, playcanvas.Vec2.ZERO, name);
4832
5744
  break;
4833
- case 'text':
4834
- this.text = newValue ?? '';
5745
+ case 'linear-motion-x':
5746
+ this.linearMotionX = parseEnum(newValue, ['locked', 'limited', 'free'], 'locked', name);
4835
5747
  break;
4836
- case 'texture-asset':
4837
- this.textureAsset = newValue ?? '';
5748
+ case 'linear-motion-y':
5749
+ this.linearMotionY = parseEnum(newValue, ['locked', 'limited', 'free'], 'locked', name);
4838
5750
  break;
4839
- case 'type':
4840
- this.type = parseEnum(newValue, ['group', 'image', 'text'], 'group', name);
5751
+ case 'linear-motion-z':
5752
+ this.linearMotionZ = parseEnum(newValue, ['locked', 'limited', 'free'], 'locked', name);
4841
5753
  break;
4842
- case 'use-input':
4843
- this.useInput = parseBool(newValue, false);
5754
+ case 'linear-stiffness':
5755
+ this.linearStiffness = parseVec3(newValue, playcanvas.Vec3.ZERO, name);
4844
5756
  break;
4845
- case 'width':
4846
- this.width = parseNumber(newValue, 0, name);
5757
+ case 'max-motor-force':
5758
+ this.maxMotorForce = parseNumber(newValue, 0, name);
4847
5759
  break;
4848
- case 'wrap-lines':
4849
- this.wrapLines = parseBool(newValue, false);
5760
+ case 'motor-speed':
5761
+ this.motorSpeed = parseNumber(newValue, 0, name);
5762
+ break;
5763
+ case 'swing-limit-y':
5764
+ this.swingLimitY = parseNumber(newValue, 45, name);
5765
+ break;
5766
+ case 'swing-limit-z':
5767
+ this.swingLimitZ = parseNumber(newValue, 45, name);
5768
+ break;
5769
+ case 'twist-limit':
5770
+ this.twistLimit = parseNumber(newValue, 20, name);
5771
+ break;
5772
+ case 'type':
5773
+ this.type = parseEnum(newValue, ['fixed', 'ball', 'hinge', 'slider', '6dof'], 'fixed', name);
4850
5774
  break;
4851
5775
  }
4852
5776
  }
4853
5777
  }
4854
- customElements.define('pc-element', ElementComponentElement);
5778
+ customElements.define('pc-joint', JointComponentElement);
4855
5779
 
4856
5780
  /**
4857
5781
  * The LayoutChildComponentElement interface provides properties and methods for manipulating
@@ -6057,6 +6981,7 @@ class MaterialElement extends HTMLElement {
6057
6981
  _metalnessMapRotation = 0;
6058
6982
  _metalnessMapTiling = new playcanvas.Vec2(1, 1);
6059
6983
  _metalnessMapUv = 0;
6984
+ _name = 'Untitled';
6060
6985
  _normalMap = '';
6061
6986
  _normalMapOffset = new playcanvas.Vec2(0, 0);
6062
6987
  _normalMapRotation = 0;
@@ -6174,6 +7099,7 @@ class MaterialElement extends HTMLElement {
6174
7099
  material.metalnessMapRotation = this._metalnessMapRotation;
6175
7100
  material.metalnessMapTiling = this._metalnessMapTiling;
6176
7101
  material.metalnessMapUv = this._metalnessMapUv;
7102
+ material.name = this._name;
6177
7103
  material.normalMapOffset = this._normalMapOffset;
6178
7104
  material.normalMapRotation = this._normalMapRotation;
6179
7105
  material.normalMapTiling = this._normalMapTiling;
@@ -7260,6 +8186,26 @@ class MaterialElement extends HTMLElement {
7260
8186
  get metalnessMapUv() {
7261
8187
  return this._metalnessMapUv;
7262
8188
  }
8189
+ /**
8190
+ * Sets the name of the material.
8191
+ * @param value - The material name.
8192
+ */
8193
+ set name(value) {
8194
+ this._name = value;
8195
+ if (this.material) {
8196
+ // A label rather than shader state, so no update() is scheduled
8197
+ this.material.name = value;
8198
+ }
8199
+ }
8200
+ /**
8201
+ * Gets the name of the material - the label shown wherever materials surface by name, such
8202
+ * as profilers, GPU captures and the assignments `pc-model.hierarchy()` reports. Purely a
8203
+ * label: element references resolve through `id`.
8204
+ * @returns The material name.
8205
+ */
8206
+ get name() {
8207
+ return this._name;
8208
+ }
7263
8209
  /**
7264
8210
  * Sets the id of the `pc-asset` to use as the normal map.
7265
8211
  * @param value - The asset id.
@@ -7831,6 +8777,7 @@ class MaterialElement extends HTMLElement {
7831
8777
  'metalness-map-rotation',
7832
8778
  'metalness-map-tiling',
7833
8779
  'metalness-map-uv',
8780
+ 'name',
7834
8781
  'normal-map',
7835
8782
  'normal-map-offset',
7836
8783
  'normal-map-rotation',
@@ -8031,6 +8978,9 @@ class MaterialElement extends HTMLElement {
8031
8978
  case 'metalness-map-uv':
8032
8979
  this.metalnessMapUv = parseNumber(newValue, 0, name);
8033
8980
  break;
8981
+ case 'name':
8982
+ this.name = newValue ?? 'Untitled';
8983
+ break;
8034
8984
  case 'normal-map':
8035
8985
  this.normalMap = newValue ?? '';
8036
8986
  break;
@@ -10576,6 +11526,41 @@ class GSplatComponentElement extends ComponentElement {
10576
11526
  }
10577
11527
  customElements.define('pc-gsplat', GSplatComponentElement);
10578
11528
 
11529
+ /**
11530
+ * Formats one line of the printable hierarchy: the node's name, an `[index]` marker when the
11531
+ * name is shared by several nodes in the model, the attached component types, and the material
11532
+ * names of a render component.
11533
+ *
11534
+ * @param node - The node to format.
11535
+ * @param counts - The number of nodes bearing each name.
11536
+ * @returns The formatted line.
11537
+ */
11538
+ const formatNode = (node, counts) => {
11539
+ const index = (counts.get(node.name) ?? 0) > 1 ? ` [${node.index}]` : '';
11540
+ const components = node.components.length > 0 ? ` (${node.components.join(', ')})` : '';
11541
+ // Braces rather than brackets: `[N]` already means a match index on this line
11542
+ const materials = node.materials.length > 0 ? ` {${node.materials.map((slot) => slot.name ?? 'null').join(', ')}}` : '';
11543
+ return `${node.name}${index}${components}${materials}`;
11544
+ };
11545
+ /**
11546
+ * Formats the printable form of a hierarchy subtree.
11547
+ *
11548
+ * @param root - The subtree root.
11549
+ * @param counts - The number of nodes bearing each name.
11550
+ * @returns The tree, one line per node.
11551
+ */
11552
+ const formatHierarchy = (root, counts) => {
11553
+ const lines = [formatNode(root, counts)];
11554
+ const walk = (node, prefix) => {
11555
+ node.children.forEach((child, i) => {
11556
+ const last = i === node.children.length - 1;
11557
+ lines.push(`${prefix}${last ? '└─ ' : '├─ '}${formatNode(child, counts)}`);
11558
+ walk(child, `${prefix}${last ? ' ' : '│ '}`);
11559
+ });
11560
+ };
11561
+ walk(root, '');
11562
+ return lines.join('\n');
11563
+ };
10579
11564
  /**
10580
11565
  * The ModelElement interface provides properties and methods for manipulating
10581
11566
  * {@link https://developer.playcanvas.com/user-manual/web-components/tags/pc-model/ | `<pc-model>`} elements.
@@ -10622,6 +11607,57 @@ class ModelElement extends AsyncElement {
10622
11607
  get entity() {
10623
11608
  return this._entity;
10624
11609
  }
11610
+ /**
11611
+ * Returns a snapshot of the instantiated node tree, or `null` while there is none (the
11612
+ * container asset has not loaded, or the element has left the document). One call grounds a
11613
+ * session — a browser console, a test, an agent — in the vocabulary `pc-node` binding
11614
+ * resolves against: the instantiated names ({@link HierarchyNode.name}), paths, match
11615
+ * indices, attached component types and the material assignments of render components
11616
+ * ({@link HierarchyNode.materials}). `String(...)` of the result, or of any node in it,
11617
+ * is the printable form.
11618
+ *
11619
+ * The snapshot is plain data, computed afresh each call: it does not follow later changes
11620
+ * to the hierarchy, and mutating it changes nothing.
11621
+ *
11622
+ * @returns The root of the instantiated node tree, or `null`.
11623
+ */
11624
+ hierarchy() {
11625
+ const root = this._entity;
11626
+ if (!root) {
11627
+ return null;
11628
+ }
11629
+ // Ordinals are assigned in the traversal resolution searches — pre-order depth-first
11630
+ // from the model root, the root itself included — so each node's index is exactly what
11631
+ // a pc-node's index attribute selects. Once the walk completes, the map holds the total
11632
+ // count per name, which is what the printable form reads to annotate only shared names.
11633
+ const ordinals = new Map();
11634
+ const describe = (entity, pathBelowRoot) => {
11635
+ const index = ordinals.get(entity.name) ?? 0;
11636
+ ordinals.set(entity.name, index + 1);
11637
+ const node = {
11638
+ name: entity.name,
11639
+ // The root has no path below itself; its own name stands in, as it does for
11640
+ // the path a pc-node bound to the root reports.
11641
+ path: pathBelowRoot || entity.name,
11642
+ index,
11643
+ // A plain GraphNode grafted into the hierarchy has no component storage
11644
+ components: Object.keys(entity.c ?? {}).sort(),
11645
+ materials: (entity.render?.meshInstances ?? []).map((meshInstance, slot) => ({
11646
+ index: slot,
11647
+ name: meshInstance.material?.name ?? null
11648
+ })),
11649
+ children: entity.children.map((child) => describe(child, pathBelowRoot ? `${pathBelowRoot}/${child.name}` : child.name))
11650
+ };
11651
+ // Non-enumerable, keeping the snapshot plain data under JSON.stringify, spreads and
11652
+ // key enumeration. Deferred to call time, by which the ordinal map holds its totals.
11653
+ Object.defineProperty(node, 'toString', {
11654
+ enumerable: false,
11655
+ value: () => formatHierarchy(node, ordinals)
11656
+ });
11657
+ return node;
11658
+ };
11659
+ return describe(root, '');
11660
+ }
10625
11661
  connectedCallback() {
10626
11662
  // A model outside an application is inert and never becomes ready, so awaiting it hangs.
10627
11663
  // Warn rather than fail silently, naming the parent it requires, as every other misplaced
@@ -10780,6 +11816,74 @@ class ModelElement extends AsyncElement {
10780
11816
  }
10781
11817
  customElements.define('pc-model', ModelElement);
10782
11818
 
11819
+ /**
11820
+ * Parses one mapping into its valid rules, warning for each entry that is not one: an unknown
11821
+ * or missing selector prefix, an empty `name:` value, an `index:` value that is not a
11822
+ * non-negative integer, or a replacement id that is not a non-empty string. An invalid rule
11823
+ * behaves exactly as if absent from the mapping.
11824
+ *
11825
+ * @param overrides - The mapping to parse.
11826
+ * @param label - The element description for warnings.
11827
+ * @returns The valid rules.
11828
+ */
11829
+ const parseMaterialRules = (overrides, label) => {
11830
+ const rules = [];
11831
+ for (const [selector, id] of Object.entries(overrides)) {
11832
+ if (typeof id !== 'string' || id === '') {
11833
+ console.warn(`${label} material-overrides '${selector}' needs a pc-material id - rule ignored`);
11834
+ }
11835
+ else if (selector.startsWith('name:')) {
11836
+ // The text after the prefix is the selector value, exactly as written - a material
11837
+ // name may legitimately begin or end with whitespace
11838
+ const name = selector.slice('name:'.length);
11839
+ if (name === '') {
11840
+ console.warn(`${label} material-overrides 'name:' selector is empty - rule ignored`);
11841
+ }
11842
+ else {
11843
+ rules.push({ kind: 'name', name, id });
11844
+ }
11845
+ }
11846
+ else if (selector.startsWith('index:')) {
11847
+ // Whitespace around the number is tolerated; Number('') is 0, so blank means NaN
11848
+ const text = selector.slice('index:'.length).trim();
11849
+ const index = text === '' ? NaN : Number(text);
11850
+ if (!Number.isInteger(index) || index < 0) {
11851
+ console.warn(`${label} material-overrides '${selector}' is not a non-negative integer index - rule ignored`);
11852
+ }
11853
+ else {
11854
+ rules.push({ kind: 'index', index, id });
11855
+ }
11856
+ }
11857
+ else {
11858
+ console.warn(`${label} material-overrides '${selector}' has no 'name:' or 'index:' prefix - rule ignored`);
11859
+ }
11860
+ }
11861
+ return rules;
11862
+ };
11863
+ /**
11864
+ * Parses the material-overrides attribute text. Anything but a JSON object — malformed JSON, an
11865
+ * array, a primitive — warns and yields `null`, the absent mapping: a stale mapping must not
11866
+ * survive an attribute value the DOM no longer represents.
11867
+ *
11868
+ * @param text - The attribute text.
11869
+ * @param label - The element description for warnings.
11870
+ * @returns The mapping, or `null`.
11871
+ */
11872
+ const parseMaterialOverridesAttribute = (text, label) => {
11873
+ let parsed;
11874
+ try {
11875
+ parsed = JSON.parse(text);
11876
+ }
11877
+ catch (error) {
11878
+ console.warn(`${label} material-overrides is not valid JSON - treated as absent: ${error.message}`);
11879
+ return null;
11880
+ }
11881
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
11882
+ console.warn(`${label} material-overrides must be a JSON object - treated as absent`);
11883
+ return null;
11884
+ }
11885
+ return parsed;
11886
+ };
10783
11887
  /**
10784
11888
  * Computes the Levenshtein distance between two strings, for near-miss suggestions in the
10785
11889
  * resolution warnings.
@@ -10835,6 +11939,13 @@ const levenshtein = (a, b) => {
10835
11939
  * "x y z" triple.
10836
11940
  * @attribute {string} scale - Overrides the node's local scale, as an "x y z" triple.
10837
11941
  * @attribute {string} tags - Overrides the node's tags, separated by spaces or commas.
11942
+ * @attribute {string} material-overrides - Overrides material assignments on the bound node's
11943
+ * render component, as a JSON object from selector to `pc-material` id — for example
11944
+ * `{"name:CarPaint": "candy-red", "index:7": "smoked-glass"}`. A `name:X` key selects every mesh
11945
+ * instance whose baseline material is named `X`; an `index:N` key selects mesh instance `N` and
11946
+ * wins over a name rule for the same instance. Assignments no rule matches keep their baseline
11947
+ * materials, and removing the attribute restores all of them. Use `pc-model.hierarchy()` to
11948
+ * discover the names and indices a node offers.
10838
11949
  * @attribute {string} onpointerenter - Script to run when the pointer moves onto the node.
10839
11950
  * @attribute {string} onpointerleave - Script to run when the pointer moves off the node.
10840
11951
  * @attribute {string} onpointermove - Script to run when the pointer moves over the node.
@@ -10870,12 +11981,25 @@ class NodeElement extends EntityBaseElement {
10870
11981
  _destroyHandle = null;
10871
11982
  /** The authored values displaced by this element's overrides, captured per property. */
10872
11983
  _authored = {};
11984
+ /**
11985
+ * The model-authored render component of the bound node, recorded at bind — before child
11986
+ * decorations build — so a render component added later by a child `pc-render` can never
11987
+ * become the override target. `null` when the bound node has none.
11988
+ */
11989
+ _authoredRender = null;
11990
+ /**
11991
+ * The baseline assignments displaced by the material overrides, captured for every mesh
11992
+ * instance when the first non-empty mapping applies and released when the mapping goes
11993
+ * absent (restoring them) or the binding dissolves.
11994
+ */
11995
+ _baseline = null;
10873
11996
  // Override values. `null` means "no override": the authored value stays in force.
10874
11997
  _enabled = null;
10875
11998
  _position = null;
10876
11999
  _rotation = null;
10877
12000
  _scale = null;
10878
12001
  _tags = null;
12002
+ _materialOverrides = null;
10879
12003
  /**
10880
12004
  * The binding state: `pending` until the host instantiates and `name` resolves, `bound`
10881
12005
  * once decorated, `missing`/`ambiguous`/`duplicate` when resolution failed (each also
@@ -11009,6 +12133,7 @@ class NodeElement extends EntityBaseElement {
11009
12133
  this._destroyHandle = target.once('destroy', this._onEntityDestroy, this);
11010
12134
  this._state = 'bound';
11011
12135
  this._path = this._pathOf(target, hostEntity);
12136
+ this._authoredRender = target.render ?? null;
11012
12137
  this._applyOverrides();
11013
12138
  this._onReady();
11014
12139
  this._buildChildren();
@@ -11039,6 +12164,7 @@ class NodeElement extends EntityBaseElement {
11039
12164
  this._entity = null;
11040
12165
  this._path = null;
11041
12166
  this._authored = {};
12167
+ this._authoredRender = null;
11042
12168
  // Component decorations come off through the same hook the host-ready cycle uses. A
11043
12169
  // dissolve that never rebinds fires no ready event, so the sweep is explicit - after
11044
12170
  // `_entity` is cleared, so the hook sees a host without an entity.
@@ -11060,6 +12186,9 @@ class NodeElement extends EntityBaseElement {
11060
12186
  this._entity = null;
11061
12187
  this._path = null;
11062
12188
  this._authored = {};
12189
+ this._authoredRender = null;
12190
+ // The mesh instances died with the entity - the capture is dropped, not restored
12191
+ this._baseline = null;
11063
12192
  this._state = 'pending';
11064
12193
  this._resetReady();
11065
12194
  }
@@ -11074,13 +12203,7 @@ class NodeElement extends EntityBaseElement {
11074
12203
  if (!app) {
11075
12204
  return;
11076
12205
  }
11077
- const childEntities = this.querySelectorAll('pc-entity');
11078
- childEntities.forEach((child) => {
11079
- child._createEntity(app);
11080
- });
11081
- childEntities.forEach((child) => {
11082
- child._buildHierarchy(app);
11083
- });
12206
+ buildDescendantEntities(this, app);
11084
12207
  }
11085
12208
  /**
11086
12209
  * Applies every override that is explicitly set, capturing the authored value it displaces.
@@ -11101,6 +12224,9 @@ class NodeElement extends EntityBaseElement {
11101
12224
  if (this._tags !== null) {
11102
12225
  this.tags = this._tags;
11103
12226
  }
12227
+ if (this._materialOverrides !== null) {
12228
+ this._applyMaterialOverrides();
12229
+ }
11104
12230
  }
11105
12231
  /**
11106
12232
  * Restores every authored value this element's overrides displaced. The override values
@@ -11126,6 +12252,104 @@ class NodeElement extends EntityBaseElement {
11126
12252
  entity.tags.add(authored.tags);
11127
12253
  }
11128
12254
  this._authored = {};
12255
+ this._restoreBaseline();
12256
+ }
12257
+ /**
12258
+ * Applies the material mapping to the authored render component: parse the mapping's valid
12259
+ * rules, capture the baseline on first application, then recompute every assignment from
12260
+ * that baseline - name rules write over it, index rules write over them, so `index:` wins -
12261
+ * and assign whatever changed. An absent mapping, or one with no valid rules, restores the
12262
+ * baseline instead. Called while bound, from `_applyOverrides` and the property setter.
12263
+ */
12264
+ _applyMaterialOverrides() {
12265
+ const label = `pc-node '${this._name}'`;
12266
+ const rules = this._materialOverrides ? parseMaterialRules(this._materialOverrides, label) : [];
12267
+ if (rules.length === 0) {
12268
+ this._restoreBaseline();
12269
+ return;
12270
+ }
12271
+ if (!this._baseline) {
12272
+ if (!this._authoredRender) {
12273
+ console.warn(`${label} is bound to a node without an authored render component - material-overrides ignored`);
12274
+ return;
12275
+ }
12276
+ this._baseline = this._authoredRender.meshInstances.map((meshInstance) => ({
12277
+ meshInstance,
12278
+ material: meshInstance.material ?? null,
12279
+ name: meshInstance.material?.name ?? null
12280
+ }));
12281
+ }
12282
+ const baseline = this._baseline;
12283
+ /** Resolves a replacement id, warning when it does not resolve. */
12284
+ const resolveReplacement = (id) => {
12285
+ const material = MaterialElement.get(id);
12286
+ if (!material) {
12287
+ console.warn(`${label} material-overrides could not resolve pc-material '${id}' - rule ignored`);
12288
+ }
12289
+ return material ?? null;
12290
+ };
12291
+ // Recompute the whole list from the baseline: name rules write over it, index rules
12292
+ // write over them. Recomputing makes mapping edits order-independent, and a rule whose
12293
+ // replacement does not resolve simply leaves the layer below it in force.
12294
+ const resolved = baseline.map((assignment) => assignment.material);
12295
+ for (const rule of rules) {
12296
+ if (rule.kind !== 'name') {
12297
+ continue;
12298
+ }
12299
+ const material = resolveReplacement(rule.id);
12300
+ if (!material) {
12301
+ continue;
12302
+ }
12303
+ let matched = false;
12304
+ baseline.forEach((assignment, index) => {
12305
+ if (assignment.name === rule.name) {
12306
+ resolved[index] = material;
12307
+ matched = true;
12308
+ }
12309
+ });
12310
+ if (!matched) {
12311
+ const names = baseline.map((assignment) => `'${assignment.name}'`).join(', ');
12312
+ console.warn(`${label} material-overrides 'name:${rule.name}' matches no assignment - ` +
12313
+ `baseline names: ${names || '(none)'}`);
12314
+ }
12315
+ }
12316
+ for (const rule of rules) {
12317
+ if (rule.kind !== 'index') {
12318
+ continue;
12319
+ }
12320
+ if (rule.index >= baseline.length) {
12321
+ console.warn(`${label} material-overrides 'index:${rule.index}' is out of range - ` +
12322
+ `${baseline.length} assignment(s)`);
12323
+ continue;
12324
+ }
12325
+ const material = resolveReplacement(rule.id);
12326
+ if (material) {
12327
+ resolved[rule.index] = material;
12328
+ }
12329
+ }
12330
+ baseline.forEach((assignment, index) => {
12331
+ // The engine setter rebuilds material and shader state even for a redundant write,
12332
+ // so only actual changes are assigned
12333
+ if (assignment.meshInstance.material !== resolved[index]) {
12334
+ assignment.meshInstance.material = resolved[index];
12335
+ }
12336
+ });
12337
+ }
12338
+ /**
12339
+ * Restores every baseline assignment the material overrides displaced and releases the
12340
+ * capture, so the next non-empty mapping captures afresh. Safe to call without a capture.
12341
+ */
12342
+ _restoreBaseline() {
12343
+ const baseline = this._baseline;
12344
+ if (!baseline) {
12345
+ return;
12346
+ }
12347
+ this._baseline = null;
12348
+ for (const assignment of baseline) {
12349
+ if (assignment.meshInstance.material !== assignment.material) {
12350
+ assignment.meshInstance.material = assignment.material;
12351
+ }
12352
+ }
11129
12353
  }
11130
12354
  /**
11131
12355
  * Renders the path of `node` below `root`, for the `path` property and the resolution
@@ -11346,8 +12570,41 @@ class NodeElement extends EntityBaseElement {
11346
12570
  get tags() {
11347
12571
  return this._tags;
11348
12572
  }
12573
+ /**
12574
+ * Sets the material overrides: a sparse mapping from selector to `pc-material` id, applied
12575
+ * to the bound node's authored render component. A `name:X` key selects every mesh instance
12576
+ * whose baseline material is named `X`; an `index:N` key selects mesh instance `N` and wins
12577
+ * over a name rule for the same instance. Assignments no rule matches keep their baseline
12578
+ * materials. `null` clears the mapping, restoring every baseline assignment.
12579
+ * @param value - The mapping, or `null`.
12580
+ */
12581
+ set materialOverrides(value) {
12582
+ // Copied and frozen: later caller mutation of the passed object must not silently
12583
+ // disagree with the mapping the element applied
12584
+ this._materialOverrides = value === null ? null : Object.freeze({ ...value });
12585
+ if (this._state === 'bound') {
12586
+ this._applyMaterialOverrides();
12587
+ }
12588
+ }
12589
+ /**
12590
+ * Gets the material overrides.
12591
+ * @returns The mapping, or `null` while no override is set.
12592
+ */
12593
+ get materialOverrides() {
12594
+ return this._materialOverrides;
12595
+ }
11349
12596
  static get observedAttributes() {
11350
- return ['enabled', 'index', 'name', 'position', 'rotation', 'scale', 'tags', ...POINTER_ATTRIBUTES];
12597
+ return [
12598
+ 'enabled',
12599
+ 'index',
12600
+ 'material-overrides',
12601
+ 'name',
12602
+ 'position',
12603
+ 'rotation',
12604
+ 'scale',
12605
+ 'tags',
12606
+ ...POINTER_ATTRIBUTES
12607
+ ];
11351
12608
  }
11352
12609
  attributeChangedCallback(name, _oldValue, newValue) {
11353
12610
  switch (name) {
@@ -11372,6 +12629,10 @@ class NodeElement extends EntityBaseElement {
11372
12629
  }
11373
12630
  }
11374
12631
  break;
12632
+ case 'material-overrides':
12633
+ this.materialOverrides =
12634
+ newValue === null ? null : parseMaterialOverridesAttribute(newValue, `pc-node '${this._name}'`);
12635
+ break;
11375
12636
  case 'name':
11376
12637
  this.name = newValue ?? '';
11377
12638
  break;
@@ -11929,6 +13190,7 @@ exports.ElementComponentElement = ElementComponentElement;
11929
13190
  exports.EntityBaseElement = EntityBaseElement;
11930
13191
  exports.EntityElement = EntityElement;
11931
13192
  exports.GSplatComponentElement = GSplatComponentElement;
13193
+ exports.JointComponentElement = JointComponentElement;
11932
13194
  exports.LayoutChildComponentElement = LayoutChildComponentElement;
11933
13195
  exports.LayoutGroupComponentElement = LayoutGroupComponentElement;
11934
13196
  exports.LightComponentElement = LightComponentElement;