@oicl/openbridge-webcomponents 2.0.0-next.170 → 2.0.0-next.171

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.
Files changed (54) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +870 -441
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +1333 -392
  4. package/dist/ar/poi/poi-aton.d.ts +1 -0
  5. package/dist/ar/poi/poi-aton.d.ts.map +1 -1
  6. package/dist/ar/poi/poi-aton.js +1 -0
  7. package/dist/ar/poi/poi-aton.js.map +1 -1
  8. package/dist/ar/poi/poi-base.d.ts +39 -5
  9. package/dist/ar/poi/poi-base.d.ts.map +1 -1
  10. package/dist/ar/poi/poi-base.js +108 -37
  11. package/dist/ar/poi/poi-base.js.map +1 -1
  12. package/dist/ar/poi/poi-data.d.ts +1 -0
  13. package/dist/ar/poi/poi-data.d.ts.map +1 -1
  14. package/dist/ar/poi/poi-data.js +1 -0
  15. package/dist/ar/poi/poi-data.js.map +1 -1
  16. package/dist/ar/poi/poi-vessel.d.ts +1 -0
  17. package/dist/ar/poi/poi-vessel.d.ts.map +1 -1
  18. package/dist/ar/poi/poi-vessel.js +1 -0
  19. package/dist/ar/poi/poi-vessel.js.map +1 -1
  20. package/dist/ar/poi/poi.d.ts +4 -12
  21. package/dist/ar/poi/poi.d.ts.map +1 -1
  22. package/dist/ar/poi/poi.js +6 -72
  23. package/dist/ar/poi/poi.js.map +1 -1
  24. package/dist/ar/poi-button/poi-button.d.ts +6 -2
  25. package/dist/ar/poi-button/poi-button.d.ts.map +1 -1
  26. package/dist/ar/poi-button/poi-button.js +10 -1
  27. package/dist/ar/poi-button/poi-button.js.map +1 -1
  28. package/dist/ar/poi-card/poi-card.d.ts +12 -1
  29. package/dist/ar/poi-card/poi-card.d.ts.map +1 -1
  30. package/dist/ar/poi-card/poi-card.js +4 -1
  31. package/dist/ar/poi-card/poi-card.js.map +1 -1
  32. package/dist/ar/poi-controller/poi-controller.d.ts +45 -1
  33. package/dist/ar/poi-controller/poi-controller.d.ts.map +1 -1
  34. package/dist/ar/poi-controller/poi-controller.js +72 -14
  35. package/dist/ar/poi-controller/poi-controller.js.map +1 -1
  36. package/dist/ar/poi-group/poi-group.d.ts +28 -5
  37. package/dist/ar/poi-group/poi-group.d.ts.map +1 -1
  38. package/dist/ar/poi-group/poi-group.js +50 -7
  39. package/dist/ar/poi-group/poi-group.js.map +1 -1
  40. package/dist/ar/poi-layer/poi-layer.css.js +69 -18
  41. package/dist/ar/poi-layer/poi-layer.css.js.map +1 -1
  42. package/dist/ar/poi-layer/poi-layer.d.ts +64 -5
  43. package/dist/ar/poi-layer/poi-layer.d.ts.map +1 -1
  44. package/dist/ar/poi-layer/poi-layer.js +173 -39
  45. package/dist/ar/poi-layer/poi-layer.js.map +1 -1
  46. package/dist/ar/poi-layer-stack/poi-layer-stack.d.ts +82 -7
  47. package/dist/ar/poi-layer-stack/poi-layer-stack.d.ts.map +1 -1
  48. package/dist/ar/poi-layer-stack/poi-layer-stack.js +172 -100
  49. package/dist/ar/poi-layer-stack/poi-layer-stack.js.map +1 -1
  50. package/dist/ar/poi-projection/poi-projection.d.ts +88 -0
  51. package/dist/ar/poi-projection/poi-projection.d.ts.map +1 -0
  52. package/dist/ar/poi-projection/poi-projection.js +90 -0
  53. package/dist/ar/poi-projection/poi-projection.js.map +1 -0
  54. package/package.json +2 -2
@@ -5776,16 +5776,22 @@ var ObcPoiButton = class ObcPoiButton extends i$4 {
5776
5776
  this.data = [];
5777
5777
  this.hasRelation = false;
5778
5778
  this.headerContent = "";
5779
+ this.handleLightSlotChange = () => {
5780
+ this.requestUpdate();
5781
+ this.syncSlottedHeaderState();
5782
+ };
5779
5783
  this.handleHeaderSlotChange = () => {
5780
5784
  this.syncSlottedHeaderState();
5781
5785
  };
5782
5786
  }
5783
5787
  connectedCallback() {
5784
5788
  super.connectedCallback();
5789
+ this.addEventListener("slotchange", this.handleLightSlotChange);
5785
5790
  this.setupHeaderContentObserver();
5786
5791
  }
5787
5792
  disconnectedCallback() {
5788
5793
  super.disconnectedCallback();
5794
+ this.removeEventListener("slotchange", this.handleLightSlotChange);
5789
5795
  this.headerContentObserver?.disconnect();
5790
5796
  this.headerContentObserver = void 0;
5791
5797
  }
@@ -5831,7 +5837,10 @@ var ObcPoiButton = class ObcPoiButton extends i$4 {
5831
5837
  }
5832
5838
  }
5833
5839
  get hasHeaderContent() {
5834
- return this.querySelector("[slot=\"header\"]") !== null;
5840
+ for (const child of this.querySelectorAll("[slot=\"header\"]")) if (child instanceof HTMLSlotElement) {
5841
+ if (child.assignedElements({ flatten: true }).length > 0) return true;
5842
+ } else return true;
5843
+ return false;
5835
5844
  }
5836
5845
  get hasGeneratedHeaderContent() {
5837
5846
  return this.headerContent.trim().length > 0;
@@ -8089,6 +8098,7 @@ var ObcPoiCard = class ObcPoiCard extends i$4 {
8089
8098
  if (!this.showHeader) return A;
8090
8099
  return b`
8091
8100
  <obc-poi-card-header
8101
+ part="header"
8092
8102
  variant=${this.headerVariant}
8093
8103
  index=${this.index}
8094
8104
  cardTitle=${this.cardTitle}
@@ -8116,6 +8126,7 @@ var ObcPoiCard = class ObcPoiCard extends i$4 {
8116
8126
  render() {
8117
8127
  return b`
8118
8128
  <div
8129
+ part="wrapper"
8119
8130
  class=${e$2({
8120
8131
  wrapper: true,
8121
8132
  "hug-content": !this.fixedSize,
@@ -8127,6 +8138,7 @@ var ObcPoiCard = class ObcPoiCard extends i$4 {
8127
8138
  <div class="center">
8128
8139
  ${this.renderLeftPointer()}
8129
8140
  <div
8141
+ part="content-box"
8130
8142
  class="content-box"
8131
8143
  role=${this.interactive ? "button" : A}
8132
8144
  tabindex=${this.interactive ? "0" : A}
@@ -8136,7 +8148,7 @@ var ObcPoiCard = class ObcPoiCard extends i$4 {
8136
8148
  @keyup=${this.handleKeyUp}
8137
8149
  >
8138
8150
  ${this.renderHeader()}
8139
- <div class="content">
8151
+ <div part="content" class="content">
8140
8152
  <slot></slot>
8141
8153
  </div>
8142
8154
  ${this.renderAlertLayer()}
@@ -8255,10 +8267,13 @@ var poi_layer_default = i$7`* {
8255
8267
 
8256
8268
  --obc-poi-layer-height: auto;
8257
8269
  --obc-poi-layer-auto-group-offset-y: 0;
8258
- --obc-poi-layer-line-length: 120px;
8259
8270
  --obc-poi-layer-exit-delay-ms: 140;
8260
8271
  --obc-poi-layer-group-removal-delay-ms: 250;
8261
8272
  --obc-poi-layer-exit-lock-duration-ms: 500;
8273
+ --obc-poi-layer-overlap-enter: 10px;
8274
+ --obc-poi-layer-overlap-exit: 18px;
8275
+ --obc-poi-layer-overlap-pre: 16px;
8276
+ --obc-poi-layer-overlap-behind: 10px;
8262
8277
  }
8263
8278
 
8264
8279
  .wrapper.debug {
@@ -8284,8 +8299,10 @@ var poi_layer_default = i$7`* {
8284
8299
  pointer-events: none;
8285
8300
  }
8286
8301
 
8287
- ::slotted([data-poi-target]),
8288
- ::slotted(obc-poi-group) {
8302
+ /* ---------- Main slot: ungrouped targets and manual groups ---------- */
8303
+
8304
+ .main::slotted([data-poi-target]),
8305
+ .main::slotted(obc-poi-group) {
8289
8306
  position: absolute;
8290
8307
  bottom: 0;
8291
8308
  height: 100%;
@@ -8305,59 +8322,105 @@ var poi_layer_default = i$7`* {
8305
8322
  pointer-events: auto;
8306
8323
  }
8307
8324
 
8308
- ::slotted([data-poi-target][data-grouped]) {
8325
+ .main::slotted([data-poi-target][data-grouped]) {
8309
8326
  opacity: 0;
8310
8327
  }
8311
8328
 
8312
- ::slotted([data-poi-target]:not([data-grouped])) {
8329
+ .main::slotted([data-poi-target]:not([data-grouped])) {
8313
8330
  opacity: 1;
8314
8331
  }
8315
8332
 
8316
- ::slotted([data-poi-target][data-behind]) {
8333
+ .main::slotted([data-poi-target][data-behind]) {
8317
8334
  opacity: 0.4;
8318
8335
  --poi-size: 32px;
8319
8336
  --obc-poi-target-icon-opacity: 0;
8320
8337
  }
8321
8338
 
8322
- ::slotted([data-poi-target][data-exiting]) {
8339
+ .main::slotted([data-poi-target][data-exiting]) {
8323
8340
  opacity: 0.4;
8324
8341
  --poi-size: 32px;
8325
8342
  --obc-poi-target-icon-opacity: 0;
8326
8343
  }
8327
8344
 
8328
- ::slotted([data-poi-target][data-front]) {
8345
+ .main::slotted([data-poi-target][data-front]) {
8329
8346
  opacity: 1;
8330
8347
  --poi-size: 36px;
8331
8348
  --obc-poi-target-icon-opacity: 1;
8332
8349
  }
8333
8350
 
8334
- ::slotted(obc-poi-group[data-auto-group]) {
8335
- opacity: 1;
8336
- }
8337
-
8338
- ::slotted(obc-poi-group) {
8351
+ .main::slotted(obc-poi-group) {
8339
8352
  bottom: 0;
8340
8353
  height: 100%;
8341
8354
  transform: none;
8342
8355
  pointer-events: none;
8343
8356
  }
8344
8357
 
8345
- ::slotted(obc-poi-group[data-auto-group]) {
8358
+ .main::slotted(obc-poi-group[data-exiting]) {
8359
+ pointer-events: none;
8360
+ }
8361
+
8362
+ /* ---------- Auto groups: shadow chrome, members slot-assigned ---------- */
8363
+
8364
+ .wrapper > obc-poi-group[data-auto-group] {
8346
8365
  opacity: 0;
8347
8366
  z-index: 2;
8348
8367
  transition: opacity 0.1s ease-out;
8349
8368
  }
8350
8369
 
8351
- ::slotted(obc-poi-group[data-auto-group][data-visible]) {
8370
+ .wrapper > obc-poi-group[data-auto-group][data-visible] {
8352
8371
  opacity: 1;
8353
8372
  }
8354
8373
 
8355
- ::slotted(obc-poi-group[data-exiting]) {
8374
+ .wrapper > obc-poi-group[data-auto-group][data-exiting] {
8356
8375
  pointer-events: none;
8357
8376
  }
8358
8377
 
8359
- :host([data-stack-inactive]) ::slotted([data-poi-target]:not([data-grouped])),
8360
- :host([data-stack-inactive]) ::slotted(obc-poi-group) {
8378
+ /*
8379
+ * Members of auto groups stay in the consumer's light DOM but are assigned
8380
+ * to the group's member slot, so they are styled from the layer stylesheet
8381
+ * (mirrors the grouped-target rules previously in poi-group.css).
8382
+ */
8383
+ .auto-group-members::slotted([data-poi-target]) {
8384
+ position: absolute;
8385
+ top: auto;
8386
+ bottom: 0;
8387
+ height: 100%;
8388
+ transform-origin: 50% 50%;
8389
+ --obc-poi-layer-target-scale: 1;
8390
+ --obc-poi-layer-target-shift: calc(
8391
+ (1 - var(--obc-poi-layer-target-scale)) * 100%
8392
+ );
8393
+ transform: translateX(var(--obc-poi-target-offset-x, 0px))
8394
+ translateY(calc(100% - var(--obc-poi-layer-target-shift)))
8395
+ scale(var(--obc-poi-layer-target-scale));
8396
+ transition: opacity 0.1s ease-out;
8397
+ pointer-events: auto;
8398
+ }
8399
+
8400
+ .auto-group-members::slotted([data-poi-target][data-behind]),
8401
+ .auto-group-members::slotted([data-poi-target][data-exiting]) {
8402
+ opacity: 0.4;
8403
+ --poi-size: 32px;
8404
+ --obc-poi-target-icon-opacity: 0;
8405
+ }
8406
+
8407
+ .auto-group-members::slotted([data-poi-target][data-front]) {
8408
+ opacity: 1;
8409
+ --poi-size: 36px;
8410
+ --obc-poi-target-icon-opacity: 1;
8411
+ }
8412
+
8413
+ .auto-group-members::slotted([data-poi-target][data-grouped]) {
8414
+ opacity: 1;
8415
+ transition: opacity 0.1s ease-out;
8416
+ }
8417
+
8418
+ /* ---------- Inactive stack layer dimming ---------- */
8419
+
8420
+ :host([data-stack-inactive])
8421
+ .main::slotted([data-poi-target]:not([data-grouped])),
8422
+ :host([data-stack-inactive]) .main::slotted(obc-poi-group),
8423
+ :host([data-stack-inactive]) .wrapper > obc-poi-group[data-auto-group] {
8361
8424
  --obc-poi-object-frame-opacity: var(--obc-poi-layer-inactive-opacity, 0.7);
8362
8425
  --obc-poi-data-content-opacity: var(--obc-poi-layer-inactive-opacity, 0.7);
8363
8426
  --obc-poi-overlap-elements-opacity: var(
@@ -8895,7 +8958,6 @@ var ObcPoiState = /* @__PURE__ */ function(ObcPoiState) {
8895
8958
  ObcPoiState["Alarm"] = "alarm";
8896
8959
  return ObcPoiState;
8897
8960
  }({});
8898
- var DEFAULT_LINE_LENGTH_PX = 96;
8899
8961
  var POINTER_BOX_BASE_SIZE_PX = 32;
8900
8962
  var POINT_POINTER_OFFSET_PX = 12;
8901
8963
  var ObcPoi = class ObcPoi extends i$4 {
@@ -8915,7 +8977,7 @@ var ObcPoi = class ObcPoi extends i$4 {
8915
8977
  this.pointerState = null;
8916
8978
  this.relativeDirection = 0;
8917
8979
  this.x = 0;
8918
- this.y = DEFAULT_LINE_LENGTH_PX;
8980
+ this.y = 192;
8919
8981
  this.buttonY = 0;
8920
8982
  this.fixedTarget = false;
8921
8983
  this.buttonOffsetX = 0;
@@ -8926,37 +8988,6 @@ var ObcPoi = class ObcPoi extends i$4 {
8926
8988
  this.animatePosition = false;
8927
8989
  this.slottedButton = null;
8928
8990
  }
8929
- connectedCallback() {
8930
- super.connectedCallback();
8931
- this.setupHeaderObserver();
8932
- }
8933
- disconnectedCallback() {
8934
- super.disconnectedCallback();
8935
- this.headerObserver?.disconnect();
8936
- this.headerObserver = void 0;
8937
- }
8938
- setupHeaderObserver() {
8939
- this.headerObserver?.disconnect();
8940
- this.headerObserver = new MutationObserver((mutations) => {
8941
- for (const mutation of mutations) {
8942
- if (mutation.type === "childList") {
8943
- this.syncFallbackButtonHeaderContent();
8944
- this.syncAttachedHeaderState();
8945
- return;
8946
- }
8947
- if (mutation.type === "attributes" && mutation.target instanceof HTMLElement && mutation.target.getAttribute("slot") === "header") {
8948
- this.syncFallbackButtonHeaderContent();
8949
- this.syncAttachedHeaderState();
8950
- return;
8951
- }
8952
- }
8953
- });
8954
- this.headerObserver.observe(this, {
8955
- childList: true,
8956
- attributes: true,
8957
- attributeFilter: ["slot"]
8958
- });
8959
- }
8960
8991
  updatePosition() {
8961
8992
  this.style.removeProperty("top");
8962
8993
  if (this.fixedTarget) {
@@ -8991,15 +9022,6 @@ var ObcPoi = class ObcPoi extends i$4 {
8991
9022
  default: return ObcPoiButtonState.Enabled;
8992
9023
  }
8993
9024
  }
8994
- get resolvedHeaderState() {
8995
- switch (this.buttonState) {
8996
- case ObcPoiButtonState.Caution: return ObcPoiHeaderState.Caution;
8997
- case ObcPoiButtonState.Warning: return ObcPoiHeaderState.Warning;
8998
- case ObcPoiButtonState.Alarm: return ObcPoiHeaderState.Alarm;
8999
- case ObcPoiButtonState.Enabled:
9000
- default: return this.selected ? ObcPoiHeaderState.Selected : ObcPoiHeaderState.Enabled;
9001
- }
9002
- }
9003
9025
  get lineStyle() {
9004
9026
  if (this.state === "alarm") return POIStyle.Alarm;
9005
9027
  if (this.state === "warning") return POIStyle.Warning;
@@ -9109,31 +9131,11 @@ var ObcPoi = class ObcPoi extends i$4 {
9109
9131
  return this.type === "point" ? Math.max(POINT_POINTER_OFFSET_PX, targetDelta) : targetDelta;
9110
9132
  }
9111
9133
  handleButtonSlotChange(e) {
9112
- const assigned = e.target.assignedElements({ flatten: false });
9134
+ if (e.target !== e.currentTarget) return;
9135
+ const assigned = e.currentTarget.assignedElements({ flatten: false });
9113
9136
  this.slottedButton = assigned.length > 0 ? assigned[0] : null;
9114
- this.syncFallbackButtonHeaderContent();
9115
9137
  this.syncSlottedButtonProps();
9116
9138
  }
9117
- syncFallbackButtonHeaderContent() {
9118
- if (this.slottedButton) return;
9119
- const button = this.renderRoot.querySelector("obc-poi-button.poi-button");
9120
- if (!button) return;
9121
- for (const child of Array.from(this.children)) {
9122
- if (!(child instanceof HTMLElement) || child.getAttribute("slot") !== "header") continue;
9123
- if (child.parentElement !== button) button.appendChild(child);
9124
- this.applyHeaderState(child);
9125
- }
9126
- }
9127
- applyHeaderState(root) {
9128
- const headers = [...root instanceof Element && root.matches("obc-poi-header") ? [root] : [], ...root.querySelectorAll("obc-poi-header")];
9129
- for (const header of headers) {
9130
- header.state = this.resolvedHeaderState;
9131
- header.setAttribute("state", this.resolvedHeaderState);
9132
- }
9133
- }
9134
- syncAttachedHeaderState() {
9135
- this.applyHeaderState(this.renderRoot);
9136
- }
9137
9139
  syncSlottedButtonProps() {
9138
9140
  const btn = this.slottedButton;
9139
9141
  if (!btn) return;
@@ -9166,6 +9168,7 @@ var ObcPoi = class ObcPoi extends i$4 {
9166
9168
  .data=${this.data}
9167
9169
  >
9168
9170
  <slot></slot>
9171
+ <slot name="header" slot="header"></slot>
9169
9172
  </obc-poi-button>
9170
9173
  </slot>
9171
9174
  `;
@@ -9191,10 +9194,6 @@ var ObcPoi = class ObcPoi extends i$4 {
9191
9194
  </div>
9192
9195
  `;
9193
9196
  }
9194
- firstUpdated(_changedProperties) {
9195
- this.syncFallbackButtonHeaderContent();
9196
- this.syncAttachedHeaderState();
9197
- }
9198
9197
  updated(changedProperties) {
9199
9198
  super.updated(changedProperties);
9200
9199
  if (changedProperties.has("x")) {
@@ -9202,9 +9201,7 @@ var ObcPoi = class ObcPoi extends i$4 {
9202
9201
  this.style.setProperty("--obc-poi-x", `${this.x}px`);
9203
9202
  }
9204
9203
  if (changedProperties.has("buttonY") || changedProperties.has("y") || changedProperties.has("fixedTarget")) this.updatePosition();
9205
- this.syncFallbackButtonHeaderContent();
9206
9204
  this.syncSlottedButtonProps();
9207
- this.syncAttachedHeaderState();
9208
9205
  }
9209
9206
  static {
9210
9207
  this.styles = r$7(poi_default);
@@ -9281,7 +9278,6 @@ ObcPoi = __decorate([customElement("obc-poi")], ObcPoi);
9281
9278
  function stripWhitespaceTextNodes(el) {
9282
9279
  for (const node of Array.from(el.childNodes)) if (node.nodeType === Node.TEXT_NODE && (node.textContent ?? "").trim() === "") node.remove();
9283
9280
  }
9284
- var X_FILTER_CUTOFF_HZ = 16;
9285
9281
  var X_FILTER_DEADBAND_PX = .1;
9286
9282
  var X_MOVING_HINT_MS = 120;
9287
9283
  var VALID_POI_TYPES = new Set(Object.values(ObcPoiType));
@@ -9294,12 +9290,29 @@ var VALID_POI_STATES = new Set(Object.values(ObcPoiState));
9294
9290
  * layout-change dispatch, and visual-query logic so that each variant
9295
9291
  * only needs to override `renderContent()` and `getVisualNodes()`.
9296
9292
  *
9293
+ * ### Coordinate Model
9294
+ * - `x` is the horizontal center position in px within the layer.
9295
+ * - Targets are anchored at the layer's bottom edge; `y` is the connector
9296
+ * line length in px extending downward from that anchor to the target
9297
+ * point (default `192`, the shared `DEFAULT_LINE_LENGTH_PX`). The button
9298
+ * renders at the top of the connector, inside the layer.
9299
+ * - `buttonY` positions the button; its interaction with `fixedTarget` is
9300
+ * described in the `obc-poi` documentation.
9301
+ * - `x` and `y` updates pass through built-in low-pass filters (see
9302
+ * `xFilterCutoffHz` and `yFilterCutoffHz`).
9303
+ *
9297
9304
  * The `@fires` tag below documents the dispatch site, which lives here rather
9298
9305
  * than in the variants. It is deliberately duplicated on each concrete
9299
9306
  * subclass — that is where `custom-elements.json` and the framework wrappers
9300
9307
  * pick the event up, since this class is not registered. Do not remove it from
9301
9308
  * the subclasses. `@ignore` keeps this class out of the generated docs.
9302
9309
  *
9310
+ * @property xFilterCutoffHz - Cutoff frequency (Hz) of the built-in low-pass filter applied to `x`.
9311
+ * Lower values smooth noisy input more aggressively; `0` or a negative
9312
+ * value disables filtering so `x` is applied directly.
9313
+ * @property yFilterCutoffHz - Cutoff frequency (Hz) of the built-in low-pass filter applied to `y`.
9314
+ * Lower values smooth noisy input more aggressively; `0` or a negative
9315
+ * value disables filtering so `y` is applied directly.
9303
9316
  * @fires {CustomEvent<void>} obc-poi-data-layout-change - Fired when layout-driving properties change. Bubbles and is composed.
9304
9317
  * @ignore
9305
9318
  */
@@ -9324,6 +9337,8 @@ var PoiBase = class extends i$4 {
9324
9337
  this.x = 0;
9325
9338
  this.y = 192;
9326
9339
  this.buttonY = 0;
9340
+ this.xFilterCutoffHz = 16;
9341
+ this.yFilterCutoffHz = 16;
9327
9342
  this.fixedTarget = false;
9328
9343
  this.boxWidth = null;
9329
9344
  this.boxHeight = null;
@@ -9335,12 +9350,18 @@ var PoiBase = class extends i$4 {
9335
9350
  this.lastXFilterTimestampMs = 0;
9336
9351
  this.xFilterRaf = 0;
9337
9352
  this.xMovingHintTimeout = null;
9353
+ this.filteredY = 0;
9354
+ this.yFilterTarget = 0;
9355
+ this.yFilterInitialized = false;
9356
+ this.lastYFilterTimestampMs = 0;
9357
+ this.yFilterRaf = 0;
9338
9358
  this.stepXFilter = (nowMs) => {
9339
9359
  this.xFilterRaf = 0;
9340
9360
  if (!this.isConnected || !this.xFilterInitialized) return;
9341
9361
  const dtSeconds = this.lastXFilterTimestampMs > 0 ? clamp$1((nowMs - this.lastXFilterTimestampMs) / 1e3, 1 / 120, .25) : 1 / 60;
9342
9362
  this.lastXFilterTimestampMs = nowMs;
9343
- const alpha = 1 - Math.exp(-2 * Math.PI * X_FILTER_CUTOFF_HZ * dtSeconds);
9363
+ const cutoffHz = this.resolvedXFilterCutoffHz;
9364
+ const alpha = cutoffHz <= 0 ? 1 : 1 - Math.exp(-2 * Math.PI * cutoffHz * dtSeconds);
9344
9365
  const delta = this.xFilterTarget - this.filteredX;
9345
9366
  const nextX = Math.abs(delta) <= X_FILTER_DEADBAND_PX ? this.xFilterTarget : this.filteredX + delta * alpha;
9346
9367
  const changed = Math.abs(nextX - this.filteredX) > 1e-6;
@@ -9359,6 +9380,26 @@ var PoiBase = class extends i$4 {
9359
9380
  this.markXMoving();
9360
9381
  this.xFilterRaf = requestAnimationFrame(this.stepXFilter);
9361
9382
  };
9383
+ this.stepYFilter = (nowMs) => {
9384
+ this.yFilterRaf = 0;
9385
+ if (!this.isConnected || !this.yFilterInitialized) return;
9386
+ const dtSeconds = this.lastYFilterTimestampMs > 0 ? clamp$1((nowMs - this.lastYFilterTimestampMs) / 1e3, 1 / 120, .25) : 1 / 60;
9387
+ this.lastYFilterTimestampMs = nowMs;
9388
+ const cutoffHz = this.resolvedYFilterCutoffHz;
9389
+ const alpha = cutoffHz <= 0 ? 1 : 1 - Math.exp(-2 * Math.PI * cutoffHz * dtSeconds);
9390
+ const delta = this.yFilterTarget - this.filteredY;
9391
+ const nextY = Math.abs(delta) <= X_FILTER_DEADBAND_PX ? this.yFilterTarget : this.filteredY + delta * alpha;
9392
+ const changed = Math.abs(nextY - this.filteredY) > 1e-6;
9393
+ this.filteredY = nextY;
9394
+ if (changed) this.applyFilteredY();
9395
+ if (Math.abs(this.yFilterTarget - this.filteredY) <= X_FILTER_DEADBAND_PX) {
9396
+ this.filteredY = this.yFilterTarget;
9397
+ this.applyFilteredY();
9398
+ this.lastYFilterTimestampMs = 0;
9399
+ return;
9400
+ }
9401
+ this.yFilterRaf = requestAnimationFrame(this.stepYFilter);
9402
+ };
9362
9403
  }
9363
9404
  connectedCallback() {
9364
9405
  stripWhitespaceTextNodes(this);
@@ -9386,6 +9427,12 @@ var PoiBase = class extends i$4 {
9386
9427
  this._targetOffsetX = nextValue;
9387
9428
  this.requestUpdate("targetOffsetX", oldValue);
9388
9429
  }
9430
+ get resolvedXFilterCutoffHz() {
9431
+ return Number.isFinite(this.xFilterCutoffHz) ? this.xFilterCutoffHz : 16;
9432
+ }
9433
+ get resolvedYFilterCutoffHz() {
9434
+ return Number.isFinite(this.yFilterCutoffHz) ? this.yFilterCutoffHz : 16;
9435
+ }
9389
9436
  dispatchLayoutChange() {
9390
9437
  this.dispatchEvent(new CustomEvent("obc-poi-data-layout-change", {
9391
9438
  bubbles: true,
@@ -9401,7 +9448,7 @@ var PoiBase = class extends i$4 {
9401
9448
  this.markXMoving();
9402
9449
  return;
9403
9450
  }
9404
- if (Math.abs(nextX - this.filteredX) <= X_FILTER_DEADBAND_PX) {
9451
+ if (this.resolvedXFilterCutoffHz <= 0 || Math.abs(nextX - this.filteredX) <= X_FILTER_DEADBAND_PX) {
9405
9452
  this.filteredX = this.xFilterTarget;
9406
9453
  this.style.setProperty("--obc-poi-data-x", `${this.filteredX}px`);
9407
9454
  if (this.xFilterRaf) {
@@ -9418,6 +9465,33 @@ var PoiBase = class extends i$4 {
9418
9465
  this.xFilterRaf = requestAnimationFrame(this.stepXFilter);
9419
9466
  }
9420
9467
  }
9468
+ applyFilteredY() {
9469
+ this.requestUpdate();
9470
+ this.updatePosition();
9471
+ this.dispatchLayoutChange();
9472
+ }
9473
+ syncYFilterTarget(nextY) {
9474
+ this.yFilterTarget = nextY;
9475
+ if (!this.yFilterInitialized) {
9476
+ this.yFilterInitialized = true;
9477
+ this.filteredY = nextY;
9478
+ return;
9479
+ }
9480
+ if (this.resolvedYFilterCutoffHz <= 0 || Math.abs(nextY - this.filteredY) <= X_FILTER_DEADBAND_PX) {
9481
+ this.filteredY = this.yFilterTarget;
9482
+ this.applyFilteredY();
9483
+ if (this.yFilterRaf) {
9484
+ cancelAnimationFrame(this.yFilterRaf);
9485
+ this.yFilterRaf = 0;
9486
+ }
9487
+ this.lastYFilterTimestampMs = 0;
9488
+ return;
9489
+ }
9490
+ if (!this.yFilterRaf) {
9491
+ this.lastYFilterTimestampMs = 0;
9492
+ this.yFilterRaf = requestAnimationFrame(this.stepYFilter);
9493
+ }
9494
+ }
9421
9495
  markXMoving() {
9422
9496
  this.setAttribute("data-x-moving", "true");
9423
9497
  if (this.xMovingHintTimeout !== null) window.clearTimeout(this.xMovingHintTimeout);
@@ -9435,6 +9509,11 @@ var PoiBase = class extends i$4 {
9435
9509
  this.xFilterRaf = 0;
9436
9510
  }
9437
9511
  this.lastXFilterTimestampMs = 0;
9512
+ if (this.yFilterRaf) {
9513
+ cancelAnimationFrame(this.yFilterRaf);
9514
+ this.yFilterRaf = 0;
9515
+ }
9516
+ this.lastYFilterTimestampMs = 0;
9438
9517
  if (this.xMovingHintTimeout !== null) {
9439
9518
  window.clearTimeout(this.xMovingHintTimeout);
9440
9519
  this.xMovingHintTimeout = null;
@@ -9446,12 +9525,10 @@ var PoiBase = class extends i$4 {
9446
9525
  for (const mutation of mutations) {
9447
9526
  if (mutation.type === "childList") {
9448
9527
  this.syncHeaderContent();
9449
- this.syncHeaderState();
9450
9528
  return;
9451
9529
  }
9452
9530
  if (mutation.type === "attributes" && mutation.target instanceof HTMLElement && mutation.target.getAttribute("slot") === "header") {
9453
9531
  this.syncHeaderContent();
9454
- this.syncHeaderState();
9455
9532
  return;
9456
9533
  }
9457
9534
  }
@@ -9464,17 +9541,16 @@ var PoiBase = class extends i$4 {
9464
9541
  }
9465
9542
  updated(changedProperties) {
9466
9543
  if (changedProperties.has("x")) this.syncXFilterTarget(Number.isFinite(this.x) ? this.x : 0);
9544
+ if (changedProperties.has("y")) this.syncYFilterTarget(Number.isFinite(this.y) ? this.y : 0);
9467
9545
  if (changedProperties.has("buttonY") || changedProperties.has("y") || changedProperties.has("fixedTarget") || changedProperties.has("selected") || changedProperties.has("type")) {
9468
9546
  this.updatePosition();
9469
9547
  this.syncInnerPoiLayout();
9470
9548
  }
9471
9549
  if (changedProperties.has("x") || changedProperties.has("buttonY") || changedProperties.has("y") || changedProperties.has("buttonOffsetX") || changedProperties.has("targetOffsetX") || changedProperties.has("fixedTarget") || changedProperties.has("selected") || changedProperties.has("type")) this.dispatchLayoutChange();
9472
9550
  this.syncHeaderContent();
9473
- this.syncHeaderState();
9474
9551
  }
9475
9552
  firstUpdated(_changedProperties) {
9476
9553
  this.syncHeaderContent();
9477
- this.syncHeaderState();
9478
9554
  }
9479
9555
  updatePosition() {
9480
9556
  if (this.fixedTarget) {
@@ -9578,8 +9654,9 @@ var PoiBase = class extends i$4 {
9578
9654
  const { poi } = this.getVisualNodes();
9579
9655
  return poi?.shadowRoot?.querySelector("obc-poi-pointer.pointer") ?? null;
9580
9656
  }
9581
- /** Public `y` normalized as the target Y input. */
9657
+ /** Public `y` normalized as the target Y input, after the y filter. */
9582
9658
  get resolvedTargetY() {
9659
+ if (this.yFilterInitialized) return this.filteredY;
9583
9660
  return Number.isFinite(this.y) ? this.y : 0;
9584
9661
  }
9585
9662
  /** Public `buttonY` normalized as the button Y input. */
@@ -9611,38 +9688,20 @@ var PoiBase = class extends i$4 {
9611
9688
  .data=${this.data}
9612
9689
  >
9613
9690
  <slot></slot>
9691
+ <slot name="header" slot="header"></slot>
9614
9692
  </obc-poi-button>
9615
9693
  `;
9616
9694
  }
9617
- get resolvedHeaderState() {
9618
- switch (this.resolvedPoiState) {
9619
- case ObcPoiState.Caution: return ObcPoiHeaderState.Caution;
9620
- case ObcPoiState.Warning: return ObcPoiHeaderState.Warning;
9621
- case ObcPoiState.Alarm: return ObcPoiHeaderState.Alarm;
9622
- case ObcPoiState.Enabled:
9623
- default: return this.selected ? ObcPoiHeaderState.Selected : ObcPoiHeaderState.Enabled;
9624
- }
9625
- }
9695
+ /**
9696
+ * Auto-enable `hasHeader` when the consumer slots header content directly
9697
+ * (outside a stack, which manages `hasHeader` itself). Header elements stay
9698
+ * in the consumer's light DOM and reach the inner button through the
9699
+ * `<slot name="header">` forwarding chain; their `state` is synced by
9700
+ * `obc-poi-button`.
9701
+ */
9626
9702
  syncHeaderContent() {
9627
- const headerChildren = Array.from(this.children).filter((child) => child instanceof HTMLElement && child.getAttribute("slot") === "header");
9628
- if (headerChildren.length === 0 && !this.hasHeader) return;
9629
- if (headerChildren.length > 0 && !this.hasHeader && this.closest("obc-poi-layer-stack") === null) this.hasHeader = true;
9630
- const target = this.shadowRoot?.querySelector("[slot=\"button\"]") ?? this.shadowRoot?.querySelector("obc-poi");
9631
- if (!target) return;
9632
- for (const child of headerChildren) {
9633
- if (child.parentElement !== target) target.appendChild(child);
9634
- this.applyHeaderState(child);
9635
- }
9636
- }
9637
- applyHeaderState(root) {
9638
- const headers = [...root instanceof Element && root.matches("obc-poi-header") ? [root] : [], ...root.querySelectorAll("obc-poi-header")];
9639
- for (const header of headers) {
9640
- header.state = this.resolvedHeaderState;
9641
- header.setAttribute("state", this.resolvedHeaderState);
9642
- }
9643
- }
9644
- syncHeaderState() {
9645
- this.applyHeaderState(this.renderRoot);
9703
+ if (this.hasHeader) return;
9704
+ if (Array.from(this.children).some((child) => child instanceof HTMLElement && child.getAttribute("slot") === "header") && this.closest("obc-poi-layer-stack") === null) this.hasHeader = true;
9646
9705
  }
9647
9706
  render() {
9648
9707
  return b`
@@ -9719,6 +9778,14 @@ __decorate([n$4({
9719
9778
  type: Number,
9720
9779
  attribute: "button-y"
9721
9780
  })], PoiBase.prototype, "buttonY", void 0);
9781
+ __decorate([n$4({
9782
+ type: Number,
9783
+ attribute: "x-filter-cutoff-hz"
9784
+ })], PoiBase.prototype, "xFilterCutoffHz", void 0);
9785
+ __decorate([n$4({
9786
+ type: Number,
9787
+ attribute: "y-filter-cutoff-hz"
9788
+ })], PoiBase.prototype, "yFilterCutoffHz", void 0);
9722
9789
  __decorate([n$4({
9723
9790
  type: Boolean,
9724
9791
  attribute: "fixed-target"
@@ -9817,6 +9884,7 @@ var ObcPoiData = class ObcPoiData extends PoiBase {
9817
9884
  <slot
9818
9885
  ><obi-vessel-generic-default-filled></obi-vessel-generic-default-filled
9819
9886
  ></slot>
9887
+ <slot name="header" slot="header"></slot>
9820
9888
  </obc-poi-button-data>
9821
9889
  `;
9822
9890
  }
@@ -10117,9 +10185,36 @@ var ObcPoiGroup = class ObcPoiGroup extends i$4 {
10117
10185
  this.wrapperHasValues = false;
10118
10186
  }
10119
10187
  }
10188
+ /**
10189
+ * Membership check that works for both light-DOM children (manual groups)
10190
+ * and slot-assigned members (layer-created auto groups, where the member
10191
+ * stays in the consumer's light DOM and is assigned into this group's
10192
+ * member slot).
10193
+ */
10194
+ isMember(child) {
10195
+ return child.parentElement === this || child.assignedSlot?.parentElement === this;
10196
+ }
10197
+ getMemberSlot() {
10198
+ return this.querySelector(":scope > slot.auto-group-members");
10199
+ }
10200
+ /**
10201
+ * Paint the front child on top. For light-DOM members this reorders the
10202
+ * children; for slot-assigned members it re-assigns the member slot, which
10203
+ * changes flattened-tree order without touching the consumer's DOM.
10204
+ */
10205
+ bringChildToFront(frontChild) {
10206
+ const memberSlot = this.getMemberSlot();
10207
+ if (memberSlot && frontChild.assignedSlot === memberSlot) {
10208
+ const members = memberSlot.assignedElements();
10209
+ if (members[members.length - 1] === frontChild) return;
10210
+ memberSlot.assign(...members.filter((el) => el !== frontChild), frontChild);
10211
+ return;
10212
+ }
10213
+ if (frontChild.parentElement === this) this.appendChild(frontChild);
10214
+ }
10120
10215
  ensureFrontChildOnTop() {
10121
10216
  const frontChild = this.getFrontChild();
10122
- if (frontChild && this.lastElementChild !== frontChild) this.appendChild(frontChild);
10217
+ if (frontChild && this.lastElementChild !== frontChild) this.bringChildToFront(frontChild);
10123
10218
  }
10124
10219
  onClick() {
10125
10220
  if (this.expand) return;
@@ -10128,7 +10223,7 @@ var ObcPoiGroup = class ObcPoiGroup extends i$4 {
10128
10223
  this.expand = true;
10129
10224
  }
10130
10225
  releaseTarget(target) {
10131
- if (target.parentElement !== this) return Promise.resolve(false);
10226
+ if (!this.isMember(target)) return Promise.resolve(false);
10132
10227
  if (this.pendingReleasedTarget) {
10133
10228
  if (this.pendingReleasedTarget.target === target) return this.pendingReleasedTarget.promise;
10134
10229
  return Promise.resolve(false);
@@ -10161,7 +10256,7 @@ var ObcPoiGroup = class ObcPoiGroup extends i$4 {
10161
10256
  if (this.topOffsetProgress > 0) this.wrapperVisible = false;
10162
10257
  this.lockedExpandedCenter = null;
10163
10258
  }
10164
- if (!expand && frontChild) this.appendChild(frontChild);
10259
+ if (!expand && frontChild) this.bringChildToFront(frontChild);
10165
10260
  this.animateTopOffset(expand ? 1 : 0);
10166
10261
  }
10167
10262
  calculateTopOffsetTargets(preserveCenter = false) {
@@ -10276,7 +10371,7 @@ var ObcPoiGroup = class ObcPoiGroup extends i$4 {
10276
10371
  const visualExpanded = this.collapsing ? false : progress > .5;
10277
10372
  const touchAreaExpanded = progress > .5;
10278
10373
  this.topOffsetTargets.forEach((config, child) => {
10279
- if (child.parentElement !== this) {
10374
+ if (!this.isMember(child)) {
10280
10375
  this.topOffsetTargets.delete(child);
10281
10376
  this.collapseDeltas.delete(child);
10282
10377
  this.lastAppliedOffsets.delete(child);
@@ -10404,7 +10499,7 @@ var ObcPoiGroup = class ObcPoiGroup extends i$4 {
10404
10499
  }
10405
10500
  this.lastTargetOrder = orderedTargets;
10406
10501
  targets.forEach((child) => {
10407
- if (child.parentElement !== this) {
10502
+ if (!this.isMember(child)) {
10408
10503
  this.topOffsetTargets.delete(child);
10409
10504
  this.lastAppliedOffsets.delete(child);
10410
10505
  return;
@@ -10462,7 +10557,7 @@ var ObcPoiGroup = class ObcPoiGroup extends i$4 {
10462
10557
  pending.resolve(released);
10463
10558
  }
10464
10559
  releaseTargetToParent(target) {
10465
- if (target.parentElement !== this) return false;
10560
+ if (!this.isMember(target)) return false;
10466
10561
  target.setRuntimeHorizontalOffsets?.(0, 0);
10467
10562
  if (!target.setRuntimeHorizontalOffsets) {
10468
10563
  target.buttonOffsetX = 0;
@@ -10484,6 +10579,19 @@ var ObcPoiGroup = class ObcPoiGroup extends i$4 {
10484
10579
  target.style.removeProperty("--obc-poi-group-overlap-height");
10485
10580
  target.style.removeProperty("--obc-poi-group-overlap-shift");
10486
10581
  clearPoiVisualState(target);
10582
+ const memberSlot = this.getMemberSlot();
10583
+ if (memberSlot && target.assignedSlot === memberSlot) {
10584
+ memberSlot.assign(...memberSlot.assignedElements().filter((el) => el !== target));
10585
+ const root = this.getRootNode();
10586
+ (root instanceof ShadowRoot ? root.host : null)?.releaseAssignedTarget?.(target);
10587
+ this.updatePosition();
10588
+ this.dispatchEvent(new CustomEvent("obc-poi-group-target-released", {
10589
+ detail: { target },
10590
+ bubbles: true,
10591
+ composed: true
10592
+ }));
10593
+ return true;
10594
+ }
10487
10595
  const parent = this.parentElement;
10488
10596
  if (!parent) return false;
10489
10597
  parent.insertBefore(target, this);
@@ -10502,7 +10610,10 @@ var ObcPoiGroup = class ObcPoiGroup extends i$4 {
10502
10610
  };
10503
10611
  __decorate([n$4({ type: Boolean })], ObcPoiGroup.prototype, "expand", void 0);
10504
10612
  __decorate([n$4({ type: Boolean })], ObcPoiGroup.prototype, "collapsing", void 0);
10505
- __decorate([n$4({ type: String })], ObcPoiGroup.prototype, "positionVertical", void 0);
10613
+ __decorate([n$4({
10614
+ type: String,
10615
+ attribute: "position-vertical"
10616
+ })], ObcPoiGroup.prototype, "positionVertical", void 0);
10506
10617
  __decorate([n$4({
10507
10618
  type: Boolean,
10508
10619
  attribute: "internal-swapping"
@@ -10736,6 +10847,9 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
10736
10847
  this.internalSwapping = false;
10737
10848
  this.lastHeight = 0;
10738
10849
  this.isGrouping = false;
10850
+ this.lastGroupingSignature = "";
10851
+ this.groupingIds = /* @__PURE__ */ new WeakMap();
10852
+ this.nextGroupingId = 1;
10739
10853
  this.targetObservers = /* @__PURE__ */ new Map();
10740
10854
  this.targetSizeElements = /* @__PURE__ */ new Map();
10741
10855
  this.groupingRaf = 0;
@@ -10780,6 +10894,12 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
10780
10894
  };
10781
10895
  this.buttonOffsetXResetRaf = /* @__PURE__ */ new Map();
10782
10896
  }
10897
+ static {
10898
+ this.shadowRootOptions = {
10899
+ ...i$4.shadowRootOptions,
10900
+ slotAssignment: "manual"
10901
+ };
10902
+ }
10783
10903
  static {
10784
10904
  this.GROUP_LAYER_HOOK_ATTR = "data-in-poi-layer";
10785
10905
  }
@@ -10981,19 +11101,24 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
10981
11101
  setupLayerMutationObserver() {
10982
11102
  this.layerMutationObserver?.disconnect();
10983
11103
  this.layerMutationObserver = new MutationObserver((mutations) => {
11104
+ let childListChanged = false;
11105
+ let relevant = false;
10984
11106
  for (const mutation of mutations) {
10985
11107
  if (mutation.type !== "childList") continue;
11108
+ childListChanged = true;
10986
11109
  const nodes = [...mutation.addedNodes, ...mutation.removedNodes];
10987
- for (const node of nodes) {
10988
- if (!(node instanceof HTMLElement)) continue;
10989
- if (this.isPoiLayerRelevantNode(node)) {
10990
- this.updateTargetObservers();
10991
- this.scheduleGrouping();
10992
- this.scheduleLayerHeightUpdate();
10993
- this.crossingTargetsDirty = true;
10994
- return;
10995
- }
11110
+ for (const node of nodes) if (node instanceof HTMLElement && this.isPoiLayerRelevantNode(node)) {
11111
+ relevant = true;
11112
+ break;
10996
11113
  }
11114
+ if (relevant) break;
11115
+ }
11116
+ if (childListChanged) this.syncSlotAssignments();
11117
+ if (relevant) {
11118
+ this.updateTargetObservers();
11119
+ this.scheduleGrouping();
11120
+ this.scheduleLayerHeightUpdate();
11121
+ this.crossingTargetsDirty = true;
10997
11122
  }
10998
11123
  });
10999
11124
  this.layerMutationObserver.observe(this, {
@@ -11005,6 +11130,68 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11005
11130
  this.scheduleGrouping();
11006
11131
  this.scheduleLayerHeightUpdate();
11007
11132
  }
11133
+ getMainSlot() {
11134
+ return this.shadowRoot?.querySelector("slot.main") ?? null;
11135
+ }
11136
+ /**
11137
+ * Assign every light-DOM child that is not currently placed in an
11138
+ * auto-group member slot to the main slot. With `slotAssignment: 'manual'`
11139
+ * nothing renders unless explicitly assigned, so this runs after render,
11140
+ * on child-list mutations, and after every grouping change.
11141
+ */
11142
+ syncSlotAssignments() {
11143
+ const mainSlot = this.getMainSlot();
11144
+ if (!mainSlot) return;
11145
+ const groupAssigned = /* @__PURE__ */ new Set();
11146
+ this.getAutoGroups().forEach((group) => {
11147
+ this.getAutoGroupMemberSlot(group)?.assignedNodes().forEach((node) => groupAssigned.add(node));
11148
+ });
11149
+ const mainNodes = Array.from(this.childNodes).filter((node) => !groupAssigned.has(node) && (node instanceof Element || node instanceof Text));
11150
+ mainSlot.assign(...mainNodes);
11151
+ }
11152
+ /**
11153
+ * Called by an auto-created `obc-poi-group` after it removed a target from
11154
+ * its member slot, so the target immediately re-renders through the main
11155
+ * slot instead of disappearing until the next grouping pass.
11156
+ * @internal
11157
+ */
11158
+ releaseAssignedTarget(_target) {
11159
+ this.syncSlotAssignments();
11160
+ this.requestGroupingUpdate();
11161
+ }
11162
+ /**
11163
+ * The auto-created `obc-poi-group` elements currently managed by this
11164
+ * layer. Auto groups are shadow-internal chrome (they never appear in the
11165
+ * consumer's light DOM); use this accessor — typically after a
11166
+ * `grouping-change` event — to reach them, e.g. to expand a group
11167
+ * programmatically.
11168
+ */
11169
+ get autoGroups() {
11170
+ return this.getAutoGroups();
11171
+ }
11172
+ getAutoGroups() {
11173
+ return Array.from(this.shadowRoot?.querySelectorAll("obc-poi-group[data-auto-group]") ?? []);
11174
+ }
11175
+ getAutoGroupMemberSlot(group) {
11176
+ return group.querySelector(":scope > slot.auto-group-members");
11177
+ }
11178
+ /** The auto group a target is currently assigned into, if any. */
11179
+ getAutoGroupOf(target) {
11180
+ const slot = target.assignedSlot;
11181
+ if (slot?.classList.contains("auto-group-members")) return slot.parentElement;
11182
+ return null;
11183
+ }
11184
+ /**
11185
+ * Members of a group in either mode: slot-assigned (shadow auto groups) or
11186
+ * light-DOM children (consumer-managed manual groups).
11187
+ */
11188
+ getGroupMembers(group) {
11189
+ if (group instanceof HTMLElement) {
11190
+ const slot = this.getAutoGroupMemberSlot(group);
11191
+ if (slot) return slot.assignedElements().filter((el) => isPoi(el));
11192
+ }
11193
+ return Array.from(group.querySelectorAll(`[${POI_ATTR}]`)).filter((node) => isPoi(node));
11194
+ }
11008
11195
  scheduleGrouping() {
11009
11196
  if (this.overlapMode === "crossing") {
11010
11197
  this.scheduleCrossingMode();
@@ -11049,6 +11236,35 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11049
11236
  this.crossingOrder = next.crossingOrder;
11050
11237
  return next.shouldContinue;
11051
11238
  }
11239
+ getGroupingId(target) {
11240
+ let id = this.groupingIds.get(target);
11241
+ if (id === void 0) {
11242
+ id = this.nextGroupingId++;
11243
+ this.groupingIds.set(target, id);
11244
+ }
11245
+ return id;
11246
+ }
11247
+ notifyGroupingChange(clusters, frontTargets, behindTargets, preGrouped) {
11248
+ const idsOf = (targets) => Array.from(targets, (target) => this.getGroupingId(target)).sort((a, b) => a - b);
11249
+ const signature = JSON.stringify({
11250
+ clusters: clusters.map((cluster) => idsOf(cluster)).sort(),
11251
+ front: idsOf(frontTargets),
11252
+ behind: idsOf(behindTargets),
11253
+ pre: idsOf(preGrouped)
11254
+ });
11255
+ if (signature === this.lastGroupingSignature) return;
11256
+ this.lastGroupingSignature = signature;
11257
+ this.dispatchEvent(new CustomEvent("grouping-change", {
11258
+ detail: {
11259
+ clusters: clusters.map((cluster) => [...cluster]),
11260
+ front: [...frontTargets],
11261
+ behind: [...behindTargets],
11262
+ pregrouped: [...preGrouped]
11263
+ },
11264
+ bubbles: true,
11265
+ composed: true
11266
+ }));
11267
+ }
11052
11268
  toggleGroupLayerHook(group, hasTargets) {
11053
11269
  if (hasTargets) group.setAttribute(_ObcPoiLayer.GROUP_LAYER_HOOK_ATTR, "");
11054
11270
  else group.removeAttribute(_ObcPoiLayer.GROUP_LAYER_HOOK_ATTR);
@@ -11073,14 +11289,18 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11073
11289
  const exitRaw = getComputedStyle(this).getPropertyValue("--obc-poi-layer-overlap-exit");
11074
11290
  const preRaw = getComputedStyle(this).getPropertyValue("--obc-poi-layer-overlap-pre");
11075
11291
  const behindRaw = getComputedStyle(this).getPropertyValue("--obc-poi-layer-overlap-behind");
11076
- const enterThreshold = Number.parseFloat(enterRaw) || 0;
11077
- const exitThreshold = Number.parseFloat(exitRaw) || Math.max(enterThreshold + 8, 8);
11078
- const preThreshold = Number.parseFloat(preRaw) || enterThreshold;
11079
- const behindThreshold = Number.parseFloat(behindRaw) || enterThreshold;
11292
+ const parseThreshold = (raw, fallback) => {
11293
+ const value = Number.parseFloat(raw);
11294
+ return Number.isFinite(value) ? value : fallback;
11295
+ };
11296
+ const enterThreshold = parseThreshold(enterRaw, 10);
11297
+ const exitThreshold = Math.max(parseThreshold(exitRaw, enterThreshold + 8), enterThreshold);
11298
+ const preThreshold = parseThreshold(preRaw, Math.max(enterThreshold, 16));
11299
+ const behindThreshold = parseThreshold(behindRaw, enterThreshold);
11080
11300
  const currentGroupByTarget = /* @__PURE__ */ new Map();
11081
11301
  targets.forEach((target) => {
11082
- const parent = target.parentElement;
11083
- if (parent?.tagName.toLowerCase() === "obc-poi-group") currentGroupByTarget.set(target, parent);
11302
+ const group = this.getAutoGroupOf(target);
11303
+ if (group) currentGroupByTarget.set(target, group);
11084
11304
  });
11085
11305
  const rects = /* @__PURE__ */ new Map();
11086
11306
  targets.forEach((target) => {
@@ -11094,7 +11314,7 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11094
11314
  }, currentGroupByTarget);
11095
11315
  const clusters = buildClusters(targets, adjacency);
11096
11316
  const behindClusters = buildClusters(targets, behindAdjacency);
11097
- const existingGroups = Array.from(this.querySelectorAll("obc-poi-group[data-auto-group]"));
11317
+ const existingGroups = this.getAutoGroups().filter((group) => !group.hasAttribute("data-exiting"));
11098
11318
  const frontTargets = /* @__PURE__ */ new Set();
11099
11319
  clusters.forEach((cluster) => {
11100
11320
  const front = this.getFrontTarget(cluster, rects);
@@ -11107,13 +11327,24 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11107
11327
  });
11108
11328
  const expandedAutoGroup = existingGroups.find((group) => group.expand === true || group.collapsing === true);
11109
11329
  if (expandedAutoGroup) {
11110
- if (this.joinWhileExpanded) this.tryJoinExpandedGroup(expandedAutoGroup, targets, rects, enterThreshold);
11330
+ const joined = this.joinWhileExpanded ? this.tryJoinExpandedGroup(expandedAutoGroup, targets, rects, enterThreshold) : null;
11331
+ if (joined) {
11332
+ const liveClusters = existingGroups.map((group) => this.getGroupMembers(group));
11333
+ const liveFront = /* @__PURE__ */ new Set();
11334
+ liveClusters.forEach((cluster) => {
11335
+ const front = this.getFrontTarget(cluster, rects);
11336
+ if (front) liveFront.add(front);
11337
+ });
11338
+ behindTargets.delete(joined);
11339
+ preGrouped.delete(joined);
11340
+ this.notifyGroupingChange(liveClusters, liveFront, behindTargets, preGrouped);
11341
+ }
11111
11342
  return;
11112
11343
  }
11113
11344
  const remainingClusters = [...clusters];
11114
11345
  const keptGroups = [];
11115
11346
  existingGroups.forEach((group) => {
11116
- const children = Array.from(group.children).filter((child) => isPoi(child));
11347
+ const children = this.getGroupMembers(group);
11117
11348
  const matchIndex = remainingClusters.findIndex((cluster) => cluster.length === children.length && cluster.every((target) => children.includes(target)));
11118
11349
  if (matchIndex >= 0) {
11119
11350
  keptGroups.push(group);
@@ -11141,7 +11372,8 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11141
11372
  });
11142
11373
  group.removeAttribute("data-visible");
11143
11374
  group.setAttribute("data-exiting", "true");
11144
- children.forEach((child) => this.appendChild(child));
11375
+ this.getAutoGroupMemberSlot(group)?.assign();
11376
+ this.syncSlotAssignments();
11145
11377
  children.forEach((child) => {
11146
11378
  if (!front || child !== front) child.getBoundingClientRect();
11147
11379
  });
@@ -11162,12 +11394,16 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11162
11394
  group.setAttribute("data-position-mode", "bottom");
11163
11395
  group.setAttribute("data-visible", "true");
11164
11396
  group.internalSwapping = !!this.internalSwapping;
11165
- group.setAttribute("positionVertical", `${this.getGroupPositionVertical(cluster, rects, layerRect, group)}px`);
11397
+ group.setAttribute("position-vertical", `${this.getGroupPositionVertical(cluster, rects, layerRect, group)}px`);
11398
+ const memberSlot = document.createElement("slot");
11399
+ memberSlot.className = "auto-group-members";
11400
+ group.appendChild(memberSlot);
11166
11401
  cluster.forEach((target) => {
11167
11402
  target.removeAttribute("data-grouped");
11168
- group.appendChild(target);
11169
11403
  });
11170
- this.appendChild(group);
11404
+ this.wrapper?.appendChild(group);
11405
+ memberSlot.assign(...cluster);
11406
+ this.syncSlotAssignments();
11171
11407
  requestAnimationFrame(() => {
11172
11408
  cluster.forEach((target) => target.setAttribute("data-grouped", "true"));
11173
11409
  });
@@ -11175,8 +11411,8 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11175
11411
  });
11176
11412
  const groupedTargets = /* @__PURE__ */ new Set();
11177
11413
  keptGroups.forEach((group) => {
11178
- Array.from(group.children).forEach((child) => {
11179
- if (isPoi(child)) groupedTargets.add(child);
11414
+ this.getGroupMembers(group).forEach((child) => {
11415
+ groupedTargets.add(child);
11180
11416
  });
11181
11417
  });
11182
11418
  targets.forEach((target) => {
@@ -11215,6 +11451,7 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11215
11451
  }
11216
11452
  });
11217
11453
  this.refreshGroupPositions(layerRect, rects);
11454
+ this.notifyGroupingChange(clusters, frontTargets, behindTargets, preGrouped);
11218
11455
  requestAnimationFrame(() => {
11219
11456
  targets.forEach((target) => target.removeAttribute("data-front-exit"));
11220
11457
  });
@@ -11225,13 +11462,13 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11225
11462
  }
11226
11463
  tryJoinExpandedGroup(group, targets, rects, enterThreshold) {
11227
11464
  const groupTargets = this.getGroupTargets(group);
11228
- if (groupTargets.length === 0) return;
11465
+ if (groupTargets.length === 0) return null;
11229
11466
  const groupTargetSet = new Set(groupTargets);
11230
- const candidates = targets.filter((target) => !groupTargetSet.has(target) && target.parentElement?.tagName.toLowerCase() !== "obc-poi-group");
11231
- if (candidates.length === 0) return;
11467
+ const candidates = targets.filter((target) => !groupTargetSet.has(target) && target.parentElement?.tagName.toLowerCase() !== "obc-poi-group" && this.getAutoGroupOf(target) === null);
11468
+ if (candidates.length === 0) return null;
11232
11469
  const useButtonRects = group.expand === true || group.collapsing === true;
11233
11470
  const groupCenters = groupTargets.map((target) => useButtonRects ? this.getTargetRect(target) : rects.get(target)).filter((rect) => !!rect).map((rect) => rect.left + rect.width / 2);
11234
- if (groupCenters.length === 0) return;
11471
+ if (groupCenters.length === 0) return null;
11235
11472
  const groupCenter = (Math.min(...groupCenters) + Math.max(...groupCenters)) / 2;
11236
11473
  let bestCandidate = null;
11237
11474
  let bestGap = Number.POSITIVE_INFINITY;
@@ -11249,10 +11486,16 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11249
11486
  }
11250
11487
  }
11251
11488
  }
11252
- if (!bestCandidate) return;
11489
+ if (!bestCandidate) return null;
11253
11490
  bestCandidate.setAttribute("data-joined-expanded", "true");
11254
11491
  const candidateRect = rects.get(bestCandidate);
11255
- if ((candidateRect?.left !== void 0 ? candidateRect.left + candidateRect.width / 2 : 0) < groupCenter) group.insertBefore(bestCandidate, groupTargets[0] ?? null);
11492
+ const insertBefore = (candidateRect?.left !== void 0 ? candidateRect.left + candidateRect.width / 2 : 0) < groupCenter;
11493
+ const memberSlot = group instanceof HTMLElement ? this.getAutoGroupMemberSlot(group) : null;
11494
+ if (memberSlot) {
11495
+ const members = memberSlot.assignedElements().filter((el) => el !== bestCandidate);
11496
+ memberSlot.assign(...insertBefore ? [bestCandidate, ...members] : [...members, bestCandidate]);
11497
+ this.syncSlotAssignments();
11498
+ } else if (insertBefore) group.insertBefore(bestCandidate, groupTargets[0] ?? null);
11256
11499
  else group.appendChild(bestCandidate);
11257
11500
  bestCandidate.setAttribute("data-grouped", "true");
11258
11501
  bestCandidate.removeAttribute("data-behind");
@@ -11268,6 +11511,7 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11268
11511
  bestCandidate.style.height = touchTarget;
11269
11512
  }
11270
11513
  group.refreshExpandedLayout?.(true);
11514
+ return bestCandidate;
11271
11515
  }
11272
11516
  applyStandaloneVisualState(target, overlap) {
11273
11517
  const isEnhanced = target.buttonType === ObcPoiButtonType.Enhanced;
@@ -11282,17 +11526,17 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11282
11526
  clearPoiVisualState(target);
11283
11527
  }
11284
11528
  refreshGroupPositions(layerRect, rects) {
11285
- Array.from(this.querySelectorAll("obc-poi-group")).forEach((group) => {
11286
- const children = Array.from(group.children).filter((child) => isPoi(child));
11529
+ [...Array.from(this.querySelectorAll("obc-poi-group")), ...this.getAutoGroups()].forEach((group) => {
11530
+ const children = this.getGroupMembers(group);
11287
11531
  this.toggleGroupLayerHook(group, children.length > 0);
11288
- const hasPositionAttr = group.hasAttribute("positionvertical") || group.hasAttribute("positionVertical");
11532
+ const hasPositionAttr = group.hasAttribute("position-vertical");
11289
11533
  if (!group.hasAttribute("data-auto-group") && !group.hasAttribute("data-position-mode")) group.setAttribute("data-position-mode", "bottom");
11290
11534
  const shouldSetPosition = group.hasAttribute("data-auto-group") || !hasPositionAttr;
11291
11535
  if (group.hasAttribute("data-auto-group")) group.internalSwapping = !!this.internalSwapping;
11292
11536
  if (children.length > 0 && shouldSetPosition) {
11293
11537
  const layerBounds = layerRect ?? this.getBoundingClientRect();
11294
11538
  const rectMap = rects ?? new Map(children.map((child) => [child, this.getTargetRectForGrouping(child, layerBounds)]));
11295
- group.setAttribute("positionVertical", `${this.getGroupPositionVertical(children, rectMap, layerBounds, group)}px`);
11539
+ group.setAttribute("position-vertical", `${this.getGroupPositionVertical(children, rectMap, layerBounds, group)}px`);
11296
11540
  }
11297
11541
  group.updatePosition?.();
11298
11542
  });
@@ -11434,19 +11678,20 @@ var ObcPoiLayer = class ObcPoiLayer extends i$4 {
11434
11678
  return Array.from(this.querySelectorAll(`[${POI_ATTR}]`)).filter((node) => isPoi(node) && !node.hasAttribute("data-stack-selected"));
11435
11679
  }
11436
11680
  getGroupTargets(group) {
11437
- return Array.from(group.querySelectorAll(`[${POI_ATTR}]`)).filter((node) => isPoi(node));
11438
- }
11439
- updated(changedProperties) {
11440
- if (changedProperties.has("isSelected")) this.dispatchEvent(new Event("layer-selection-changed", { bubbles: true }));
11681
+ return this.getGroupMembers(group);
11441
11682
  }
11442
11683
  render() {
11443
11684
  return b`
11444
11685
  <div class="wrapper${this.debug ? " debug" : ""}">
11445
11686
  ${this.debug ? b`<span class="debug-label">${this.label || "Layer"}</span>` : A}
11446
- <slot></slot>
11687
+ <slot class="main"></slot>
11447
11688
  </div>
11448
11689
  `;
11449
11690
  }
11691
+ updated(changedProperties) {
11692
+ if (changedProperties.has("isSelected")) this.dispatchEvent(new Event("layer-selection-changed", { bubbles: true }));
11693
+ this.syncSlotAssignments();
11694
+ }
11450
11695
  static {
11451
11696
  this.styles = r$7(poi_layer_default);
11452
11697
  }
@@ -11524,6 +11769,7 @@ var ObcPoiLayerStack = class ObcPoiLayerStack extends i$4 {
11524
11769
  this.handleTargetLayoutChange = () => this.schedulePlacement();
11525
11770
  this.handleLayerSelectionChanged = () => this.schedulePlacement();
11526
11771
  this.selectionMap = /* @__PURE__ */ new Map();
11772
+ this.displacedTargets = /* @__PURE__ */ new Map();
11527
11773
  this.selectionCounter = 0;
11528
11774
  this.placementRaf = 0;
11529
11775
  }
@@ -11565,6 +11811,11 @@ var ObcPoiLayerStack = class ObcPoiLayerStack extends i$4 {
11565
11811
  this.requestPoiRender(target);
11566
11812
  });
11567
11813
  this.selectionMap.clear();
11814
+ this.displacedTargets.forEach((_, target) => {
11815
+ this.clearTargetProjectionStyles(target);
11816
+ this.requestPoiRender(target);
11817
+ });
11818
+ this.displacedTargets.clear();
11568
11819
  this.selectionCounter = 0;
11569
11820
  }
11570
11821
  onStackClick(event) {
@@ -11573,25 +11824,81 @@ var ObcPoiLayerStack = class ObcPoiLayerStack extends i$4 {
11573
11824
  if (!target) return;
11574
11825
  if (target.hasAttribute(_ObcPoiLayerStack.STACK_RETURNING_ATTR)) return;
11575
11826
  this.cleanupSelection();
11576
- const originLayer = this.getTargetLayer(target);
11577
- if (!originLayer) return;
11578
- const selectedLayer = this.getLayer("selected") ?? this.getLayer("top");
11579
- if (!selectedLayer) return;
11580
11827
  const existing = this.selectionMap.get(target);
11581
11828
  if (existing) {
11582
11829
  this.resetSelectionForTarget(target, existing);
11583
11830
  return;
11584
11831
  }
11832
+ this.performSelection(target);
11833
+ }
11834
+ /** Currently selected POI targets. */
11835
+ get selectedTargets() {
11836
+ this.cleanupSelection();
11837
+ return Array.from(this.selectionMap.keys());
11838
+ }
11839
+ /**
11840
+ * Programmatically select a target, following the same flow as a user
11841
+ * click. `options.selectionId` presets the badge id shown in the
11842
+ * stack-managed header; without it the id auto-increments. Returns
11843
+ * `false` when selection is disabled, the target is mid-transition or
11844
+ * already selected, or no layer can host the selection.
11845
+ */
11846
+ selectTarget(target, options) {
11847
+ if (this.selectionMode === "none") return false;
11848
+ if (target.hasAttribute(_ObcPoiLayerStack.STACK_RETURNING_ATTR)) return false;
11849
+ this.cleanupSelection();
11850
+ if (this.selectionMap.has(target)) return false;
11851
+ return this.performSelection(target, options?.selectionId);
11852
+ }
11853
+ /**
11854
+ * Programmatically deselect a target. Returns `false` when it is not
11855
+ * selected.
11856
+ */
11857
+ deselectTarget(target) {
11858
+ const record = this.selectionMap.get(target);
11859
+ if (!record) return false;
11860
+ this.resetSelectionForTarget(target, record);
11861
+ return true;
11862
+ }
11863
+ /** Deselect all targets. */
11864
+ clearSelection() {
11865
+ Array.from(this.selectionMap.entries()).forEach(([target, record]) => {
11866
+ this.resetSelectionForTarget(target, record);
11867
+ });
11868
+ }
11869
+ performSelection(target, selectionId) {
11870
+ const homeLayer = this.getTargetLayer(target);
11871
+ if (!homeLayer) return false;
11872
+ const selectedLayer = this.getLayer("selected") ?? this.getLayer("top");
11873
+ if (!selectedLayer) return false;
11585
11874
  if (this.selectionMode === "single") {
11586
11875
  this.clearOtherTopSelections(target);
11587
11876
  this.clearSelectionMapExcept(target);
11588
11877
  }
11589
- const trackedOriginLayer = originLayer === selectedLayer ? this.inferBootstrapOriginLayer(target, selectedLayer) : originLayer;
11590
- this.trackSelectionTarget(target, trackedOriginLayer, true);
11591
- const record = this.selectionMap.get(target);
11592
- if (!record) return;
11878
+ const displaced = this.displacedTargets.get(target);
11879
+ this.displacedTargets.delete(target);
11880
+ const originLayer = displaced?.originLayer ?? (homeLayer === selectedLayer ? this.inferBootstrapOriginLayer(target, selectedLayer) : homeLayer);
11881
+ if (selectionId) target.setAttribute("data-stack-selection-id", selectionId);
11882
+ const record = {
11883
+ homeLayer,
11884
+ originLayer
11885
+ };
11886
+ this.selectionMap.set(target, record);
11593
11887
  this.activateTrackedTarget(target, record, selectedLayer);
11594
11888
  this.schedulePlacement();
11889
+ this.dispatchSelectionChange(target, null);
11890
+ return true;
11891
+ }
11892
+ dispatchSelectionChange(added, removed) {
11893
+ this.dispatchEvent(new CustomEvent("selection-change", {
11894
+ detail: {
11895
+ selected: this.selectedTargets,
11896
+ added,
11897
+ removed
11898
+ },
11899
+ bubbles: true,
11900
+ composed: true
11901
+ }));
11595
11902
  }
11596
11903
  getPoiTargetFromEvent(event) {
11597
11904
  const path = event.composedPath?.() ?? [];
@@ -11618,6 +11925,9 @@ var ObcPoiLayerStack = class ObcPoiLayerStack extends i$4 {
11618
11925
  this.selectionMap.forEach((_, target) => {
11619
11926
  if (!target.isConnected) this.selectionMap.delete(target);
11620
11927
  });
11928
+ this.displacedTargets.forEach((_, target) => {
11929
+ if (!target.isConnected) this.displacedTargets.delete(target);
11930
+ });
11621
11931
  }
11622
11932
  getLayer(kind) {
11623
11933
  const layers = this.getAllLayers();
@@ -11637,6 +11947,7 @@ var ObcPoiLayerStack = class ObcPoiLayerStack extends i$4 {
11637
11947
  if (!activeLayer) return;
11638
11948
  this.getLayerTargets(activeLayer).forEach((other) => {
11639
11949
  if (other === target) return;
11950
+ if (this.displacedTargets.has(other)) return;
11640
11951
  const record = this.selectionMap.get(other);
11641
11952
  this.resetSelectionForTarget(other, record);
11642
11953
  });
@@ -11694,25 +12005,60 @@ var ObcPoiLayerStack = class ObcPoiLayerStack extends i$4 {
11694
12005
  refreshTargetProjectionLayout(target, trackDurationMs = 0) {
11695
12006
  target.refreshProjectionLayout?.(trackDurationMs);
11696
12007
  }
11697
- getTargetButtonProjectionOffset(target) {
11698
- const raw = target.style.getPropertyValue("--obc-poi-button-projection-y");
12008
+ getInlineVarPx(target, name) {
12009
+ const raw = target.style.getPropertyValue(name);
11699
12010
  const parsed = Number.parseFloat(raw);
11700
12011
  return Number.isFinite(parsed) ? parsed : 0;
11701
12012
  }
11702
- getLayerProjectionOffset(originLayer, destinationLayer) {
11703
- if (!destinationLayer || destinationLayer === originLayer) return 0;
11704
- const originRect = originLayer.getBoundingClientRect();
11705
- return destinationLayer.getBoundingClientRect().bottom - originRect.bottom;
12013
+ /** Pixel delta between two layers' bottom edges (`to.bottom - from.bottom`). */
12014
+ layerBottomDelta(from, to) {
12015
+ if (from === to) return 0;
12016
+ return to.getBoundingClientRect().bottom - from.getBoundingClientRect().bottom;
12017
+ }
12018
+ setProjectionVar(target, name, valuePx) {
12019
+ if (Math.abs(valuePx) < .5) target.style.removeProperty(name);
12020
+ else target.style.setProperty(name, `${valuePx}px`);
12021
+ }
12022
+ /**
12023
+ * Render the target at the given projection offsets without touching its
12024
+ * DOM position. When `animate` is set and an offset actually changes, the
12025
+ * jump is FLIP-animated via the Web Animations API.
12026
+ */
12027
+ async applyTargetProjection(target, buttonOffsetPx, targetOffsetPx, animate) {
12028
+ const currentButton = this.getInlineVarPx(target, "--obc-poi-button-projection-y");
12029
+ const currentTarget = this.getInlineVarPx(target, "--obc-poi-target-projection-y");
12030
+ if (!(Math.abs(buttonOffsetPx - currentButton) >= .5 || Math.abs(targetOffsetPx - currentTarget) >= .5)) {
12031
+ this.setProjectionVar(target, "--obc-poi-button-projection-y", buttonOffsetPx);
12032
+ this.setProjectionVar(target, "--obc-poi-target-projection-y", targetOffsetPx);
12033
+ this.requestPoiRender(target);
12034
+ this.refreshTargetProjectionLayout(target);
12035
+ return;
12036
+ }
12037
+ const { button, line } = this.getAnimationElements(target);
12038
+ const beforeButtonRect = animate ? button?.getBoundingClientRect() ?? null : null;
12039
+ const beforeLineRect = animate ? line?.getBoundingClientRect() ?? null : null;
12040
+ this.setProjectionVar(target, "--obc-poi-button-projection-y", buttonOffsetPx);
12041
+ this.setProjectionVar(target, "--obc-poi-target-projection-y", targetOffsetPx);
12042
+ this.requestPoiRender(target);
12043
+ this.refreshTargetProjectionLayout(target);
12044
+ if (!animate) return;
12045
+ await this.waitForPoiRender(target);
12046
+ await this.animateLayerJump(target, beforeButtonRect, beforeLineRect);
11706
12047
  }
11707
- getTargetProjectionOffset(originLayer, selectedLayer) {
11708
- if (!selectedLayer || selectedLayer === originLayer) return 0;
11709
- const originRect = originLayer.getBoundingClientRect();
11710
- const selectedRect = selectedLayer.getBoundingClientRect();
11711
- return originRect.bottom - selectedRect.bottom;
12048
+ /**
12049
+ * The group a target currently belongs to — a light-DOM parent for
12050
+ * consumer-managed groups, or the shadow group whose member slot the
12051
+ * target is assigned to for layer-created auto groups.
12052
+ */
12053
+ getContainingGroup(target) {
12054
+ if (target.parentElement?.tagName.toLowerCase() === "obc-poi-group") return target.parentElement;
12055
+ const slotParent = target.assignedSlot?.parentElement;
12056
+ if (slotParent?.tagName.toLowerCase() === "obc-poi-group") return slotParent;
12057
+ return null;
11712
12058
  }
11713
12059
  detachTargetFromCurrentGroup(target) {
11714
12060
  const currentLayer = this.getTargetLayer(target);
11715
- const sourceGroup = target.parentElement?.tagName.toLowerCase() === "obc-poi-group" ? target.parentElement : null;
12061
+ const sourceGroup = this.getContainingGroup(target);
11716
12062
  if (!sourceGroup) return currentLayer;
11717
12063
  target.setRuntimeHorizontalOffsets?.(0, 0);
11718
12064
  if (!target.setRuntimeHorizontalOffsets) {
@@ -11799,73 +12145,34 @@ var ObcPoiLayerStack = class ObcPoiLayerStack extends i$4 {
11799
12145
  animations.forEach((a) => a.cancel());
11800
12146
  }
11801
12147
  }
11802
- async moveTargetIntoSelectedLayer(target, _originLayer, selectedLayer, animate = true) {
11803
- const { button, line } = this.getAnimationElements(target);
11804
- const beforeButtonRect = button?.getBoundingClientRect() ?? null;
11805
- const beforeLineRect = line?.getBoundingClientRect() ?? null;
11806
- if (this.getTargetLayer(target) !== selectedLayer) selectedLayer.appendChild(target);
11807
- this.requestPoiRender(target);
11808
- this.refreshTargetProjectionLayout(target);
11809
- await this.waitForPoiRender(target);
11810
- if (!animate) return;
11811
- await this.animateLayerJump(target, beforeButtonRect, beforeLineRect);
11812
- }
11813
- async moveTrackedTargetToSelectedLayer(target, record, selectedLayer, animateProjection) {
11814
- const currentLayer = this.detachTargetFromCurrentGroup(target) ?? this.getTargetLayer(target) ?? record.originLayer;
11815
- clearTargetGroupingAttributes(target);
11816
- await this.moveTargetIntoSelectedLayer(target, record.originLayer, selectedLayer, animateProjection);
11817
- if (currentLayer !== selectedLayer) currentLayer.requestGroupingUpdate();
11818
- selectedLayer.requestGroupingUpdate();
11819
- }
11820
- async syncTargetProjection(target, originLayer, selectedLayer, animate = true) {
11821
- const nextProjectionOffset = this.getLayerProjectionOffset(originLayer, selectedLayer);
11822
- const currentProjectionOffset = this.getTargetButtonProjectionOffset(target);
11823
- if (Math.abs(nextProjectionOffset - currentProjectionOffset) < .5) {
11824
- if (Math.abs(nextProjectionOffset) < .5) target.style.removeProperty("--obc-poi-button-projection-y");
11825
- else target.style.setProperty("--obc-poi-button-projection-y", `${nextProjectionOffset}px`);
11826
- this.requestPoiRender(target);
11827
- this.refreshTargetProjectionLayout(target);
11828
- return;
11829
- }
11830
- if (Math.abs(nextProjectionOffset) < .5) target.style.removeProperty("--obc-poi-button-projection-y");
11831
- else target.style.setProperty("--obc-poi-button-projection-y", `${nextProjectionOffset}px`);
11832
- this.requestPoiRender(target);
11833
- if (animate) {
11834
- this.refreshTargetProjectionLayout(target, _ObcPoiLayerStack.STACK_JUMP_DURATION_MS);
11835
- await this.waitForPoiRender(target);
11836
- return;
11837
- }
11838
- this.refreshTargetProjectionLayout(target);
11839
- }
11840
12148
  async animateTargetReturnToOrigin(target, record) {
11841
12149
  target.setAttribute(_ObcPoiLayerStack.STACK_RETURNING_ATTR, "true");
11842
12150
  try {
11843
- const currentLayer = this.detachTargetFromCurrentGroup(target) ?? this.getTargetLayer(target);
12151
+ const sourceGroupLayer = this.detachTargetFromCurrentGroup(target);
11844
12152
  clearTargetGroupingAttributes(target);
11845
- const { button: beforeBtn, line: beforeLine } = this.getAnimationElements(target);
11846
- const beforeButtonRect = beforeBtn?.getBoundingClientRect() ?? null;
11847
- const beforeLineRect = beforeLine?.getBoundingClientRect() ?? null;
11848
- target.style.removeProperty("--obc-poi-target-projection-y");
11849
- target.style.removeProperty("--obc-poi-button-projection-y");
11850
- if (target.parentElement !== record.originLayer) record.originLayer.appendChild(target);
11851
- this.requestPoiRender(target);
11852
- this.refreshTargetProjectionLayout(target);
11853
- await this.waitForPoiRender(target);
11854
- await this.animateLayerJump(target, beforeButtonRect, beforeLineRect);
12153
+ const originOffset = this.layerBottomDelta(record.homeLayer, record.originLayer);
12154
+ await this.applyTargetProjection(target, originOffset, originOffset, true);
11855
12155
  this.setSelectedTargetInteractivity(target, false);
11856
12156
  clearTargetGroupingAttributes(target);
11857
12157
  this.clearTargetSelectedId(target);
11858
- this.clearTargetProjectionStyles(target);
12158
+ if (record.homeLayer === record.originLayer) this.clearTargetProjectionStyles(target);
12159
+ else {
12160
+ target.style.removeProperty("--obc-poi-forced-target-transition-duration");
12161
+ target.style.removeProperty("--obc-poi-layer-inactive-opacity");
12162
+ target.style.removeProperty("z-index");
12163
+ this.displacedTargets.set(target, record);
12164
+ }
11859
12165
  this.requestPoiRender(target);
11860
- if (currentLayer && currentLayer !== record.originLayer) currentLayer.requestGroupingUpdate();
11861
- record.originLayer.requestGroupingUpdate();
12166
+ if (sourceGroupLayer && sourceGroupLayer !== record.homeLayer) sourceGroupLayer.requestGroupingUpdate();
12167
+ record.homeLayer.requestGroupingUpdate();
11862
12168
  this.schedulePlacement();
11863
12169
  } finally {
11864
12170
  target.removeAttribute(_ObcPoiLayerStack.STACK_RETURNING_ATTR);
12171
+ target.removeAttribute("data-stack-selected");
11865
12172
  }
11866
12173
  }
11867
12174
  setSelectedTargetInteractivity(target, selected) {
11868
- const isInAutoGroup = target.hasAttribute("data-grouped") || target.closest("obc-poi-group") !== null;
12175
+ const isInAutoGroup = target.hasAttribute("data-grouped") || this.getContainingGroup(target) !== null;
11869
12176
  target.selected = selected;
11870
12177
  if (selected) {
11871
12178
  target.style.setProperty("--obc-poi-overlap-pointer-events", "auto");
@@ -11880,30 +12187,22 @@ var ObcPoiLayerStack = class ObcPoiLayerStack extends i$4 {
11880
12187
  target.removeAttribute("data-front");
11881
12188
  }
11882
12189
  }
11883
- trackSelectionTarget(target, originLayer, keepInSelectedLayer = true) {
11884
- this.selectionMap.set(target, {
11885
- originLayer,
11886
- previousAnimatePosition: target.animatePosition ?? false,
11887
- keepInSelectedLayer
11888
- });
11889
- target.removeAttribute("data-stack-selected");
11890
- }
11891
12190
  activateTrackedTarget(target, record, selectedLayer, animateProjection = true) {
11892
- if (record.keepInSelectedLayer) {
11893
- target.removeAttribute("data-stack-selected");
11894
- if (this.getTargetLayer(target) !== selectedLayer) this.moveTrackedTargetToSelectedLayer(target, record, selectedLayer, animateProjection);
11895
- else {
11896
- const targetProjectionOffset = this.getTargetProjectionOffset(record.originLayer, selectedLayer);
11897
- if (Math.abs(targetProjectionOffset) < .5) target.style.removeProperty("--obc-poi-target-projection-y");
11898
- else target.style.setProperty("--obc-poi-target-projection-y", `${targetProjectionOffset}px`);
11899
- target.style.removeProperty("--obc-poi-button-projection-y");
11900
- this.requestPoiRender(target);
11901
- this.refreshTargetProjectionLayout(target);
11902
- }
11903
- } else {
12191
+ if (record.homeLayer !== selectedLayer) {
12192
+ const sourceGroupLayer = this.detachTargetFromCurrentGroup(target);
11904
12193
  clearTargetGroupingAttributes(target);
12194
+ const firstActivation = !target.hasAttribute("data-stack-selected");
11905
12195
  target.setAttribute("data-stack-selected", "true");
11906
- this.syncTargetProjection(target, record.originLayer, selectedLayer, animateProjection);
12196
+ const buttonOffset = this.layerBottomDelta(record.homeLayer, selectedLayer);
12197
+ const targetOffset = this.layerBottomDelta(record.homeLayer, record.originLayer);
12198
+ this.applyTargetProjection(target, buttonOffset, targetOffset, animateProjection);
12199
+ if (firstActivation) {
12200
+ if (sourceGroupLayer && sourceGroupLayer !== record.homeLayer) sourceGroupLayer.requestGroupingUpdate();
12201
+ record.homeLayer.requestGroupingUpdate();
12202
+ }
12203
+ } else {
12204
+ const targetOffset = this.layerBottomDelta(selectedLayer, record.originLayer);
12205
+ this.applyTargetProjection(target, 0, targetOffset, animateProjection);
11907
12206
  }
11908
12207
  this.setSelectedTargetInteractivity(target, true);
11909
12208
  this.setTargetSelectedId(target);
@@ -11916,25 +12215,26 @@ var ObcPoiLayerStack = class ObcPoiLayerStack extends i$4 {
11916
12215
  for (const target of selectedTargets) {
11917
12216
  if (target.hasAttribute(_ObcPoiLayerStack.STACK_RETURNING_ATTR)) continue;
11918
12217
  if (this.selectionMap.has(target)) continue;
12218
+ if (this.displacedTargets.has(target)) continue;
11919
12219
  if (this.selectionMode === "single" && this.selectionMap.size > 0) break;
11920
12220
  const originLayer = this.inferBootstrapOriginLayer(target, selectedLayer);
11921
12221
  this.selectionMap.set(target, {
11922
- originLayer,
11923
- previousAnimatePosition: target.animatePosition ?? false,
11924
- keepInSelectedLayer: true
12222
+ homeLayer: selectedLayer,
12223
+ originLayer
11925
12224
  });
11926
- target.removeAttribute("data-stack-selected");
11927
12225
  seededTargets.add(target);
12226
+ this.dispatchSelectionChange(target, null);
11928
12227
  }
11929
12228
  return seededTargets;
11930
12229
  }
11931
12230
  resetSelectionForTarget(target, record) {
11932
- target.removeAttribute("data-stack-selected");
11933
12231
  if (record) {
11934
12232
  this.selectionMap.delete(target);
11935
12233
  this.animateTargetReturnToOrigin(target, record);
12234
+ this.dispatchSelectionChange(null, target);
11936
12235
  return;
11937
12236
  }
12237
+ target.removeAttribute("data-stack-selected");
11938
12238
  this.setSelectedTargetInteractivity(target, false);
11939
12239
  clearTargetGroupingAttributes(target);
11940
12240
  this.clearTargetSelectedId(target);
@@ -11999,6 +12299,7 @@ var ObcPoiLayerStack = class ObcPoiLayerStack extends i$4 {
11999
12299
  this.selectionMap.forEach((record, target) => {
12000
12300
  this.resetSelectionForTarget(target, record);
12001
12301
  });
12302
+ this.refreshDisplacedTargets();
12002
12303
  return;
12003
12304
  }
12004
12305
  const seededTargets = this.seedSelectedLayerSelections(selectedLayer);
@@ -12008,9 +12309,25 @@ var ObcPoiLayerStack = class ObcPoiLayerStack extends i$4 {
12008
12309
  return;
12009
12310
  }
12010
12311
  if (target.hasAttribute(_ObcPoiLayerStack.STACK_RETURNING_ATTR)) return;
12011
- if (!record.keepInSelectedLayer && !target.hasAttribute("data-stack-selected")) target.setAttribute("data-stack-selected", "true");
12012
12312
  this.activateTrackedTarget(target, record, selectedLayer, !seededTargets.has(target));
12013
12313
  });
12314
+ this.refreshDisplacedTargets();
12315
+ }
12316
+ /**
12317
+ * Keep deselected targets that render outside their DOM layer (targets
12318
+ * authored in the selected layer whose logical origin is another layer)
12319
+ * projected at up-to-date offsets across layer resizes.
12320
+ */
12321
+ refreshDisplacedTargets() {
12322
+ this.displacedTargets.forEach((record, target) => {
12323
+ if (!target.isConnected) {
12324
+ this.displacedTargets.delete(target);
12325
+ return;
12326
+ }
12327
+ if (target.hasAttribute(_ObcPoiLayerStack.STACK_RETURNING_ATTR)) return;
12328
+ const originOffset = this.layerBottomDelta(record.homeLayer, record.originLayer);
12329
+ this.applyTargetProjection(target, originOffset, originOffset, false);
12330
+ });
12014
12331
  }
12015
12332
  render() {
12016
12333
  return b`
@@ -12029,7 +12346,241 @@ __decorate([n$4({
12029
12346
  })], ObcPoiLayerStack.prototype, "selectionMode", void 0);
12030
12347
  ObcPoiLayerStack = _ObcPoiLayerStack = __decorate([customElement("obc-poi-layer-stack")], ObcPoiLayerStack);
12031
12348
  //#endregion
12349
+ //#region src/ar/poi/poi-vessel.ts
12350
+ var ObcPoiVessel = class ObcPoiVessel extends PoiBase {
12351
+ constructor(..._args) {
12352
+ super(..._args);
12353
+ this.vesselType = ObcPoiObjectVesselType.Regular;
12354
+ this.vesselStyle = ObcPoiObjectVesselStyle.Regular;
12355
+ this.vesselState = null;
12356
+ this.vesselInteractive = false;
12357
+ }
12358
+ getVisualNodes() {
12359
+ return this.queryVisualNodes("obc-poi", "obc-poi-button-vessel");
12360
+ }
12361
+ renderButtonSlot() {
12362
+ return b`
12363
+ <obc-poi-button-vessel
12364
+ slot="button"
12365
+ .relativeDirection=${this.relativeDirection}
12366
+ .selected=${this.selected}
12367
+ .hasHeader=${this.hasHeader}
12368
+ .headerContent=${this.headerContent}
12369
+ .state=${this.resolvedPoiState}
12370
+ .value=${this.value}
12371
+ .overlapOpaque=${this.overlapOpaque}
12372
+ .type=${this.buttonType}
12373
+ .data=${this.data}
12374
+ .vesselType=${this.vesselType}
12375
+ .vesselStyle=${this.vesselStyle}
12376
+ .vesselState=${this.vesselState}
12377
+ .vesselInteractive=${this.vesselInteractive}
12378
+ >
12379
+ <slot
12380
+ ><obi-vessel-generic-default-filled></obi-vessel-generic-default-filled
12381
+ ></slot>
12382
+ <slot name="turn-indicator" slot="turn-indicator"></slot>
12383
+ <slot name="speed-indicator" slot="speed-indicator"></slot>
12384
+ <slot name="header" slot="header"></slot>
12385
+ </obc-poi-button-vessel>
12386
+ `;
12387
+ }
12388
+ static {
12389
+ this.styles = PoiBase.styles;
12390
+ }
12391
+ };
12392
+ __decorate([n$4({
12393
+ type: String,
12394
+ attribute: "vessel-type"
12395
+ })], ObcPoiVessel.prototype, "vesselType", void 0);
12396
+ __decorate([n$4({
12397
+ type: String,
12398
+ attribute: "vessel-style"
12399
+ })], ObcPoiVessel.prototype, "vesselStyle", void 0);
12400
+ __decorate([n$4({
12401
+ type: String,
12402
+ attribute: "vessel-state"
12403
+ })], ObcPoiVessel.prototype, "vesselState", void 0);
12404
+ __decorate([n$4({
12405
+ type: Boolean,
12406
+ attribute: "vessel-interactive"
12407
+ })], ObcPoiVessel.prototype, "vesselInteractive", void 0);
12408
+ ObcPoiVessel = __decorate([customElement("obc-poi-vessel")], ObcPoiVessel);
12409
+ //#endregion
12410
+ //#region src/icons/icon-beacon-general-east.ts
12411
+ var ObiBeaconGeneralEast = class ObiBeaconGeneralEast extends i$4 {
12412
+ constructor(..._args) {
12413
+ super(..._args);
12414
+ this.useCssColor = false;
12415
+ this.icon = w`<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
12416
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M9.17071 21H6V19.5H9.04148C9.15648 18.8145 9.50394 18.2079 10 17.7639L10 10H14V17.7639C14.4961 18.2079 14.8435 18.8145 14.9585 19.5H18V21H14.8293C14.4175 22.1652 13.3062 23 12 23C10.6938 23 9.58254 22.1652 9.17071 21ZM13.5 20C13.5 20.8284 12.8284 21.5 12 21.5C11.1716 21.5 10.5 20.8284 10.5 20C10.5 19.1716 11.1716 18.5 12 18.5C12.8284 18.5 13.5 19.1716 13.5 20Z" fill="currentColor"/>
12417
+ <path d="M14 4H10L12 1L14 4Z" fill="currentColor"/>
12418
+ <path d="M14 6H10L12 9L14 6Z" fill="currentColor"/>
12419
+ </svg>
12420
+ `;
12421
+ this.iconCss = w`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
12422
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M9.17071 21H6V19.5H9.04148C9.15648 18.8145 9.50394 18.2079 10 17.7639L10 10H14V17.7639C14.4961 18.2079 14.8435 18.8145 14.9585 19.5H18V21H14.8293C14.4175 22.1652 13.3062 23 12 23C10.6938 23 9.58254 22.1652 9.17071 21ZM13.5 20C13.5 20.8284 12.8284 21.5 12 21.5C11.1716 21.5 10.5 20.8284 10.5 20C10.5 19.1716 11.1716 18.5 12 18.5C12.8284 18.5 13.5 19.1716 13.5 20Z" style="fill: var(--element-active-color)"/>
12423
+ <path d="M14 4H10L12 1L14 4Z" style="fill: var(--element-active-color)"/>
12424
+ <path d="M14 6H10L12 9L14 6Z" style="fill: var(--element-active-color)"/>
12425
+ </svg>
12426
+ `;
12427
+ }
12428
+ render() {
12429
+ return b`
12430
+ <div class="wrapper">${this.useCssColor ? this.iconCss : this.icon}</div>
12431
+ `;
12432
+ }
12433
+ static {
12434
+ this.styles = i$7`
12435
+ .wrapper {
12436
+ height: 100%;
12437
+ width: 100%;
12438
+ line-height: 0;
12439
+ }
12440
+ .wrapper > * {
12441
+ height: 100%;
12442
+ width: 100%;
12443
+ }
12444
+ `;
12445
+ }
12446
+ };
12447
+ __decorate([n$4({ type: Boolean })], ObiBeaconGeneralEast.prototype, "useCssColor", void 0);
12448
+ ObiBeaconGeneralEast = __decorate([customElement("obi-beacon-general-east")], ObiBeaconGeneralEast);
12449
+ //#endregion
12450
+ //#region src/ar/poi/poi-aton.ts
12451
+ var ObcPoiAton = class ObcPoiAton extends PoiBase {
12452
+ constructor(..._args) {
12453
+ super(..._args);
12454
+ this.atonType = ObcPoiObjectAtonType.AtoN;
12455
+ this.atonStyle = ObcPoiObjectAtonStyle.Regular;
12456
+ this.atonState = null;
12457
+ this.atonInteractive = false;
12458
+ }
12459
+ getVisualNodes() {
12460
+ return this.queryVisualNodes("obc-poi", "obc-poi-button-aton");
12461
+ }
12462
+ renderButtonSlot() {
12463
+ return b`
12464
+ <obc-poi-button-aton
12465
+ slot="button"
12466
+ .relativeDirection=${this.relativeDirection}
12467
+ .selected=${this.selected}
12468
+ .hasHeader=${this.hasHeader}
12469
+ .headerContent=${this.headerContent}
12470
+ .state=${this.resolvedPoiState}
12471
+ .value=${this.value}
12472
+ .overlapOpaque=${this.overlapOpaque}
12473
+ .type=${this.buttonType}
12474
+ .data=${this.data}
12475
+ .atonType=${this.atonType}
12476
+ .atonStyle=${this.atonStyle}
12477
+ .atonState=${this.atonState}
12478
+ .atonInteractive=${this.atonInteractive}
12479
+ >
12480
+ <slot><obi-beacon-general-east></obi-beacon-general-east></slot>
12481
+ <slot name="header" slot="header"></slot>
12482
+ </obc-poi-button-aton>
12483
+ `;
12484
+ }
12485
+ static {
12486
+ this.styles = PoiBase.styles;
12487
+ }
12488
+ };
12489
+ __decorate([n$4({
12490
+ type: String,
12491
+ attribute: "aton-type"
12492
+ })], ObcPoiAton.prototype, "atonType", void 0);
12493
+ __decorate([n$4({
12494
+ type: String,
12495
+ attribute: "aton-style"
12496
+ })], ObcPoiAton.prototype, "atonStyle", void 0);
12497
+ __decorate([n$4({
12498
+ type: String,
12499
+ attribute: "aton-state"
12500
+ })], ObcPoiAton.prototype, "atonState", void 0);
12501
+ __decorate([n$4({
12502
+ type: Boolean,
12503
+ attribute: "aton-interactive"
12504
+ })], ObcPoiAton.prototype, "atonInteractive", void 0);
12505
+ ObcPoiAton = __decorate([customElement("obc-poi-aton")], ObcPoiAton);
12506
+ //#endregion
12507
+ //#region src/ar/poi-projection/poi-projection.ts
12508
+ /**
12509
+ * @module POI Projection
12510
+ *
12511
+ * Media-projection helpers for POI overlays.
12512
+ *
12513
+ * Maps points from media pixel space (video/image natural resolution) to
12514
+ * rendered screen space, accounting for `cover`/`contain` letterboxing.
12515
+ * This is the same math `obc-poi-controller` applies to its
12516
+ * detections-driven targets, exported as pure functions so applications
12517
+ * that manage their own POI elements can project coordinates without
12518
+ * re-implementing the transform.
12519
+ *
12520
+ * ### Usage Guidelines
12521
+ * - Build a {@link MediaProjection} once per resize/metadata change via
12522
+ * {@link computeMediaProjection}; reuse it for every point in a frame.
12523
+ * - `renderWidth`/`renderHeight` come from the media element's
12524
+ * `getBoundingClientRect()`; `mediaWidth`/`mediaHeight` from
12525
+ * `videoWidth`/`videoHeight` (video) or `naturalWidth`/`naturalHeight`
12526
+ * (image).
12527
+ * - {@link projectPointToLayer} additionally converts a projected
12528
+ * point into `obc-poi-data` layer coordinates (`x` center px,
12529
+ * `y` line length from the layer's bottom anchor), given the layer's
12530
+ * bottom offset inside the projection container.
12531
+ *
12532
+ * ### Fit modes
12533
+ * - `cover`: media fills the container; overflow is cropped.
12534
+ * - `contain`: media letterboxes inside the container.
12535
+ *
12536
+ * The Storybook demo draws the rendered container (outer box), the scaled
12537
+ * media content region (shaded), and a marker at the projected point.
12538
+ */
12539
+ /** Fit behavior matching `obc-poi-controller`'s `PoiFitMode` values. */
12540
+ var MediaFit = /* @__PURE__ */ function(MediaFit) {
12541
+ MediaFit["Cover"] = "cover";
12542
+ MediaFit["Contain"] = "contain";
12543
+ return MediaFit;
12544
+ }({});
12545
+ /**
12546
+ * Compute the projection for the given dimensions, or `null` when any
12547
+ * dimension is non-finite or zero (media not measured yet).
12548
+ */
12549
+ function computeMediaProjection(input) {
12550
+ const { mediaWidth, mediaHeight, renderWidth, renderHeight, fit } = input;
12551
+ if (!Number.isFinite(mediaWidth) || !Number.isFinite(mediaHeight) || !Number.isFinite(renderWidth) || !Number.isFinite(renderHeight) || mediaWidth === 0 || mediaHeight === 0 || renderWidth === 0 || renderHeight === 0) return null;
12552
+ const scale = fit === "cover" ? Math.max(renderWidth / mediaWidth, renderHeight / mediaHeight) : Math.min(renderWidth / mediaWidth, renderHeight / mediaHeight);
12553
+ const contentWidth = mediaWidth * scale;
12554
+ const contentHeight = mediaHeight * scale;
12555
+ return {
12556
+ scale,
12557
+ offsetX: (renderWidth - contentWidth) / 2,
12558
+ offsetY: (renderHeight - contentHeight) / 2,
12559
+ contentWidth,
12560
+ contentHeight
12561
+ };
12562
+ }
12563
+ /** Project a point from media pixel space into rendered space. */
12564
+ function projectPoint(projection, x, y) {
12565
+ return {
12566
+ x: projection.offsetX + x * projection.scale,
12567
+ y: projection.offsetY + y * projection.scale
12568
+ };
12569
+ }
12570
+ //#endregion
12032
12571
  //#region src/ar/poi-controller/poi-controller.ts
12572
+ var DETECTION_VARIANT_TAGS = {
12573
+ ["data"]: "obc-poi-data",
12574
+ ["vessel"]: "obc-poi-vessel",
12575
+ ["aton"]: "obc-poi-aton"
12576
+ };
12577
+ /**
12578
+ * Attribute consumers set on a child `obc-poi-layer` to tell the
12579
+ * controller which layer receives its detection-driven targets.
12580
+ */
12581
+ var POI_CONTROLLER_LAYER_ATTR = "data-controller-layer";
12582
+ /** `data-controller-layer` value marking the background/target layer. */
12583
+ var POI_CONTROLLER_BACKGROUND_LAYER = "background";
12033
12584
  var ObcPoiController = class ObcPoiController extends i$4 {
12034
12585
  constructor(..._args) {
12035
12586
  super(..._args);
@@ -12040,6 +12591,8 @@ var ObcPoiController = class ObcPoiController extends i$4 {
12040
12591
  this.confidenceMin = null;
12041
12592
  this.classFilter = null;
12042
12593
  this.keyFn = null;
12594
+ this.xFilterCutoffHz = null;
12595
+ this.yFilterCutoffHz = null;
12043
12596
  this.mediaWidth = 0;
12044
12597
  this.mediaHeight = 0;
12045
12598
  this.renderWidth = 0;
@@ -12111,7 +12664,7 @@ var ObcPoiController = class ObcPoiController extends i$4 {
12111
12664
  resolveTargetLayer() {
12112
12665
  const stack = this.getStackElement();
12113
12666
  if (!stack) return null;
12114
- const explicitLayer = stack.querySelector("obc-poi-layer[data-controller-layer=\"background\"]");
12667
+ const explicitLayer = stack.querySelector(`obc-poi-layer[${POI_CONTROLLER_LAYER_ATTR}="${POI_CONTROLLER_BACKGROUND_LAYER}"]`);
12115
12668
  if (explicitLayer) return explicitLayer;
12116
12669
  const layers = Array.from(stack.querySelectorAll("obc-poi-layer"));
12117
12670
  const isSelectedLayer = (candidate) => candidate.isSelected === true;
@@ -12202,17 +12755,22 @@ var ObcPoiController = class ObcPoiController extends i$4 {
12202
12755
  }
12203
12756
  return frames[0] ?? null;
12204
12757
  }
12205
- mapDetection(det) {
12206
- if (!Number.isFinite(this.mediaWidth) || !Number.isFinite(this.mediaHeight) || !Number.isFinite(this.renderWidth) || !Number.isFinite(this.renderHeight) || this.mediaWidth === 0 || this.mediaHeight === 0 || this.renderWidth === 0 || this.renderHeight === 0) return null;
12207
- const scale = this.fit === "cover" ? Math.max(this.renderWidth / this.mediaWidth, this.renderHeight / this.mediaHeight) : Math.min(this.renderWidth / this.mediaWidth, this.renderHeight / this.mediaHeight);
12208
- const contentWidth = this.mediaWidth * scale;
12209
- const contentHeight = this.mediaHeight * scale;
12210
- const offsetX = (this.renderWidth - contentWidth) / 2;
12211
- const offsetY = (this.renderHeight - contentHeight) / 2;
12758
+ computeCurrentProjection() {
12759
+ return computeMediaProjection({
12760
+ mediaWidth: this.mediaWidth,
12761
+ mediaHeight: this.mediaHeight,
12762
+ renderWidth: this.renderWidth,
12763
+ renderHeight: this.renderHeight,
12764
+ fit: this.fit === "cover" ? MediaFit.Cover : MediaFit.Contain
12765
+ });
12766
+ }
12767
+ mapDetection(det, projection = this.computeCurrentProjection()) {
12768
+ if (!projection) return null;
12769
+ const point = projectPoint(projection, det.x, det.y);
12212
12770
  return {
12213
- x: offsetX + det.x * scale,
12214
- y: offsetY + det.y * scale,
12215
- scale
12771
+ x: point.x,
12772
+ y: point.y,
12773
+ scale: projection.scale
12216
12774
  };
12217
12775
  }
12218
12776
  syncTargetsToCustomStack() {
@@ -12231,19 +12789,34 @@ var ObcPoiController = class ObcPoiController extends i$4 {
12231
12789
  const layerBottomInController = layer.getBoundingClientRect().bottom - controllerRect.top;
12232
12790
  const active = this.getActiveDetections();
12233
12791
  const activeKeys = /* @__PURE__ */ new Set();
12792
+ const projection = this.computeCurrentProjection();
12234
12793
  active.forEach((det, index) => {
12235
- const mapped = this.mapDetection(det);
12794
+ const mapped = this.mapDetection(det, projection);
12236
12795
  if (!mapped) return;
12237
12796
  const key = det.id ?? (this.keyFn ? this.keyFn(det, index) : `index:${index}`);
12238
12797
  activeKeys.add(key);
12798
+ const tag = DETECTION_VARIANT_TAGS[det.variant ?? "data"];
12239
12799
  let target = this.controllerTargets.get(key);
12800
+ if (target && target.tagName.toLowerCase() !== tag) {
12801
+ target.remove();
12802
+ this.controllerTargets.delete(key);
12803
+ target = void 0;
12804
+ }
12240
12805
  if (!target) {
12241
- target = document.createElement("obc-poi-data");
12806
+ target = document.createElement(tag);
12242
12807
  target.dataset.controller = "1";
12243
12808
  target.dataset.detectionIndex = String(index);
12244
12809
  target.fixedTarget = false;
12245
12810
  this.controllerTargets.set(key, target);
12246
12811
  }
12812
+ const iconTag = det.icon?.toLowerCase() ?? null;
12813
+ const currentIcon = target.firstElementChild;
12814
+ if (iconTag) {
12815
+ if (currentIcon?.tagName.toLowerCase() !== iconTag) {
12816
+ currentIcon?.remove();
12817
+ target.appendChild(document.createElement(iconTag));
12818
+ }
12819
+ } else if (currentIcon) currentIcon.remove();
12247
12820
  if (!target.isConnected || target.parentElement === null) layer.appendChild(target);
12248
12821
  target.dataset.detectionIndex = String(index);
12249
12822
  target.x = mapped.x;
@@ -12255,6 +12828,10 @@ var ObcPoiController = class ObcPoiController extends i$4 {
12255
12828
  target.buttonType = resolvePoiButtonTypeFromBoxSize(boxWidth, boxHeight);
12256
12829
  const heading = det.heading ?? det.direction;
12257
12830
  if (typeof heading === "number" && Number.isFinite(heading)) target.relativeDirection = heading;
12831
+ target.state = det.state ?? ObcPoiState.Enabled;
12832
+ target.data = det.data ?? [];
12833
+ target.xFilterCutoffHz = this.xFilterCutoffHz ?? 16;
12834
+ target.yFilterCutoffHz = this.yFilterCutoffHz ?? 16;
12258
12835
  });
12259
12836
  Array.from(this.controllerTargets.entries()).forEach(([key, target]) => {
12260
12837
  if (!activeKeys.has(key)) {
@@ -12286,6 +12863,14 @@ __decorate([n$4({ type: Number })], ObcPoiController.prototype, "frameIndex", vo
12286
12863
  __decorate([n$4({ type: Number })], ObcPoiController.prototype, "confidenceMin", void 0);
12287
12864
  __decorate([n$4({ type: Array })], ObcPoiController.prototype, "classFilter", void 0);
12288
12865
  __decorate([n$4({ attribute: false })], ObcPoiController.prototype, "keyFn", void 0);
12866
+ __decorate([n$4({
12867
+ type: Number,
12868
+ attribute: "x-filter-cutoff-hz"
12869
+ })], ObcPoiController.prototype, "xFilterCutoffHz", void 0);
12870
+ __decorate([n$4({
12871
+ type: Number,
12872
+ attribute: "y-filter-cutoff-hz"
12873
+ })], ObcPoiController.prototype, "yFilterCutoffHz", void 0);
12289
12874
  __decorate([r$3()], ObcPoiController.prototype, "mediaWidth", void 0);
12290
12875
  __decorate([r$3()], ObcPoiController.prototype, "mediaHeight", void 0);
12291
12876
  __decorate([r$3()], ObcPoiController.prototype, "renderWidth", void 0);
@@ -12300,162 +12885,6 @@ __decorate([o$5({
12300
12885
  })], ObcPoiController.prototype, "stackElements", void 0);
12301
12886
  ObcPoiController = __decorate([customElement("obc-poi-controller")], ObcPoiController);
12302
12887
  //#endregion
12303
- //#region src/icons/icon-beacon-general-east.ts
12304
- var ObiBeaconGeneralEast = class ObiBeaconGeneralEast extends i$4 {
12305
- constructor(..._args) {
12306
- super(..._args);
12307
- this.useCssColor = false;
12308
- this.icon = w`<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
12309
- <path fill-rule="evenodd" clip-rule="evenodd" d="M9.17071 21H6V19.5H9.04148C9.15648 18.8145 9.50394 18.2079 10 17.7639L10 10H14V17.7639C14.4961 18.2079 14.8435 18.8145 14.9585 19.5H18V21H14.8293C14.4175 22.1652 13.3062 23 12 23C10.6938 23 9.58254 22.1652 9.17071 21ZM13.5 20C13.5 20.8284 12.8284 21.5 12 21.5C11.1716 21.5 10.5 20.8284 10.5 20C10.5 19.1716 11.1716 18.5 12 18.5C12.8284 18.5 13.5 19.1716 13.5 20Z" fill="currentColor"/>
12310
- <path d="M14 4H10L12 1L14 4Z" fill="currentColor"/>
12311
- <path d="M14 6H10L12 9L14 6Z" fill="currentColor"/>
12312
- </svg>
12313
- `;
12314
- this.iconCss = w`<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
12315
- <path fill-rule="evenodd" clip-rule="evenodd" d="M9.17071 21H6V19.5H9.04148C9.15648 18.8145 9.50394 18.2079 10 17.7639L10 10H14V17.7639C14.4961 18.2079 14.8435 18.8145 14.9585 19.5H18V21H14.8293C14.4175 22.1652 13.3062 23 12 23C10.6938 23 9.58254 22.1652 9.17071 21ZM13.5 20C13.5 20.8284 12.8284 21.5 12 21.5C11.1716 21.5 10.5 20.8284 10.5 20C10.5 19.1716 11.1716 18.5 12 18.5C12.8284 18.5 13.5 19.1716 13.5 20Z" style="fill: var(--element-active-color)"/>
12316
- <path d="M14 4H10L12 1L14 4Z" style="fill: var(--element-active-color)"/>
12317
- <path d="M14 6H10L12 9L14 6Z" style="fill: var(--element-active-color)"/>
12318
- </svg>
12319
- `;
12320
- }
12321
- render() {
12322
- return b`
12323
- <div class="wrapper">${this.useCssColor ? this.iconCss : this.icon}</div>
12324
- `;
12325
- }
12326
- static {
12327
- this.styles = i$7`
12328
- .wrapper {
12329
- height: 100%;
12330
- width: 100%;
12331
- line-height: 0;
12332
- }
12333
- .wrapper > * {
12334
- height: 100%;
12335
- width: 100%;
12336
- }
12337
- `;
12338
- }
12339
- };
12340
- __decorate([n$4({ type: Boolean })], ObiBeaconGeneralEast.prototype, "useCssColor", void 0);
12341
- ObiBeaconGeneralEast = __decorate([customElement("obi-beacon-general-east")], ObiBeaconGeneralEast);
12342
- //#endregion
12343
- //#region src/ar/poi/poi-aton.ts
12344
- var ObcPoiAton = class ObcPoiAton extends PoiBase {
12345
- constructor(..._args) {
12346
- super(..._args);
12347
- this.atonType = ObcPoiObjectAtonType.AtoN;
12348
- this.atonStyle = ObcPoiObjectAtonStyle.Regular;
12349
- this.atonState = null;
12350
- this.atonInteractive = false;
12351
- }
12352
- getVisualNodes() {
12353
- return this.queryVisualNodes("obc-poi", "obc-poi-button-aton");
12354
- }
12355
- renderButtonSlot() {
12356
- return b`
12357
- <obc-poi-button-aton
12358
- slot="button"
12359
- .relativeDirection=${this.relativeDirection}
12360
- .selected=${this.selected}
12361
- .hasHeader=${this.hasHeader}
12362
- .headerContent=${this.headerContent}
12363
- .state=${this.resolvedPoiState}
12364
- .value=${this.value}
12365
- .overlapOpaque=${this.overlapOpaque}
12366
- .type=${this.buttonType}
12367
- .data=${this.data}
12368
- .atonType=${this.atonType}
12369
- .atonStyle=${this.atonStyle}
12370
- .atonState=${this.atonState}
12371
- .atonInteractive=${this.atonInteractive}
12372
- >
12373
- <slot><obi-beacon-general-east></obi-beacon-general-east></slot>
12374
- </obc-poi-button-aton>
12375
- `;
12376
- }
12377
- static {
12378
- this.styles = PoiBase.styles;
12379
- }
12380
- };
12381
- __decorate([n$4({
12382
- type: String,
12383
- attribute: "aton-type"
12384
- })], ObcPoiAton.prototype, "atonType", void 0);
12385
- __decorate([n$4({
12386
- type: String,
12387
- attribute: "aton-style"
12388
- })], ObcPoiAton.prototype, "atonStyle", void 0);
12389
- __decorate([n$4({
12390
- type: String,
12391
- attribute: "aton-state"
12392
- })], ObcPoiAton.prototype, "atonState", void 0);
12393
- __decorate([n$4({
12394
- type: Boolean,
12395
- attribute: "aton-interactive"
12396
- })], ObcPoiAton.prototype, "atonInteractive", void 0);
12397
- ObcPoiAton = __decorate([customElement("obc-poi-aton")], ObcPoiAton);
12398
- //#endregion
12399
- //#region src/ar/poi/poi-vessel.ts
12400
- var ObcPoiVessel = class ObcPoiVessel extends PoiBase {
12401
- constructor(..._args) {
12402
- super(..._args);
12403
- this.vesselType = ObcPoiObjectVesselType.Regular;
12404
- this.vesselStyle = ObcPoiObjectVesselStyle.Regular;
12405
- this.vesselState = null;
12406
- this.vesselInteractive = false;
12407
- }
12408
- getVisualNodes() {
12409
- return this.queryVisualNodes("obc-poi", "obc-poi-button-vessel");
12410
- }
12411
- renderButtonSlot() {
12412
- return b`
12413
- <obc-poi-button-vessel
12414
- slot="button"
12415
- .relativeDirection=${this.relativeDirection}
12416
- .selected=${this.selected}
12417
- .hasHeader=${this.hasHeader}
12418
- .headerContent=${this.headerContent}
12419
- .state=${this.resolvedPoiState}
12420
- .value=${this.value}
12421
- .overlapOpaque=${this.overlapOpaque}
12422
- .type=${this.buttonType}
12423
- .data=${this.data}
12424
- .vesselType=${this.vesselType}
12425
- .vesselStyle=${this.vesselStyle}
12426
- .vesselState=${this.vesselState}
12427
- .vesselInteractive=${this.vesselInteractive}
12428
- >
12429
- <slot
12430
- ><obi-vessel-generic-default-filled></obi-vessel-generic-default-filled
12431
- ></slot>
12432
- <slot name="turn-indicator" slot="turn-indicator"></slot>
12433
- <slot name="speed-indicator" slot="speed-indicator"></slot>
12434
- </obc-poi-button-vessel>
12435
- `;
12436
- }
12437
- static {
12438
- this.styles = PoiBase.styles;
12439
- }
12440
- };
12441
- __decorate([n$4({
12442
- type: String,
12443
- attribute: "vessel-type"
12444
- })], ObcPoiVessel.prototype, "vesselType", void 0);
12445
- __decorate([n$4({
12446
- type: String,
12447
- attribute: "vessel-style"
12448
- })], ObcPoiVessel.prototype, "vesselStyle", void 0);
12449
- __decorate([n$4({
12450
- type: String,
12451
- attribute: "vessel-state"
12452
- })], ObcPoiVessel.prototype, "vesselState", void 0);
12453
- __decorate([n$4({
12454
- type: Boolean,
12455
- attribute: "vessel-interactive"
12456
- })], ObcPoiVessel.prototype, "vesselInteractive", void 0);
12457
- ObcPoiVessel = __decorate([customElement("obc-poi-vessel")], ObcPoiVessel);
12458
- //#endregion
12459
12888
  //#region src/automation/valve-analoge-two-way-icon/valve-analog-two-way-icon.css?inline
12460
12889
  var valve_analog_two_way_icon_default = i$7`* {
12461
12890
  -webkit-tap-highlight-color: transparent;