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