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