@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.152 → 2.0.0-next.153
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/vitest.setup.ts +1 -1
- package/bundle/openbridge-webcomponents.bundle.js +4717 -4485
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +374 -52
- package/dist/alert-severity.d.ts +19 -1
- package/dist/alert-severity.d.ts.map +1 -1
- package/dist/alert-severity.js +48 -2
- package/dist/alert-severity.js.map +1 -1
- package/dist/components/alert-button/alert-button.css.js +7 -1513
- package/dist/components/alert-button/alert-button.css.js.map +1 -1
- package/dist/components/alert-button/alert-button.d.ts +40 -96
- package/dist/components/alert-button/alert-button.d.ts.map +1 -1
- package/dist/components/alert-button/alert-button.js +40 -107
- package/dist/components/alert-button/alert-button.js.map +1 -1
- package/dist/components/alert-button-item/alert-button-item.css.js +1570 -0
- package/dist/components/alert-button-item/alert-button-item.css.js.map +1 -0
- package/dist/components/alert-button-item/alert-button-item.d.ts +134 -0
- package/dist/components/alert-button-item/alert-button-item.d.ts.map +1 -0
- package/dist/components/alert-button-item/alert-button-item.js +210 -0
- package/dist/components/alert-button-item/alert-button-item.js.map +1 -0
- package/dist/components/alert-counter-item/alert-counter-item.css.js +25 -0
- package/dist/components/alert-counter-item/alert-counter-item.css.js.map +1 -0
- package/dist/components/alert-counter-item/alert-counter-item.d.ts +77 -0
- package/dist/components/alert-counter-item/alert-counter-item.d.ts.map +1 -0
- package/dist/components/alert-counter-item/alert-counter-item.js +87 -0
- package/dist/components/alert-counter-item/alert-counter-item.js.map +1 -0
- package/dist/components/alert-frame/alert-frame.css.js +23 -27
- package/dist/components/alert-frame/alert-frame.css.js.map +1 -1
- package/dist/components/alert-frame/alert-frame.d.ts +7 -6
- package/dist/components/alert-frame/alert-frame.d.ts.map +1 -1
- package/dist/components/alert-frame/alert-frame.js +6 -9
- package/dist/components/alert-frame/alert-frame.js.map +1 -1
- package/dist/components/tree-navigation-item/tree-navigation-item.d.ts +7 -34
- package/dist/components/tree-navigation-item/tree-navigation-item.d.ts.map +1 -1
- package/dist/components/tree-navigation-item/tree-navigation-item.js +3 -32
- package/dist/components/tree-navigation-item/tree-navigation-item.js.map +1 -1
- package/dist/generated/locales/es-419.d.ts +11 -3
- package/dist/generated/locales/es-419.d.ts.map +1 -1
- package/dist/generated/locales/es-419.js +11 -3
- package/dist/generated/locales/es-419.js.map +1 -1
- package/dist/generated/locales/fi-FI.d.ts +8 -0
- package/dist/generated/locales/fi-FI.d.ts.map +1 -1
- package/dist/generated/locales/fi-FI.js +8 -0
- package/dist/generated/locales/fi-FI.js.map +1 -1
- package/dist/navigation-instruments/readout/readout.css.js +4 -29
- package/dist/navigation-instruments/readout/readout.css.js.map +1 -1
- package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
- package/dist/navigation-instruments/readout/readout.js +1 -9
- package/dist/navigation-instruments/readout/readout.js.map +1 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js +5 -34
- package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js.map +1 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.js +1 -9
- package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
- package/dist/palettes/blinking.d.ts +6 -2
- package/dist/palettes/blinking.d.ts.map +1 -1
- package/dist/palettes/blinking.js +6 -2
- package/dist/palettes/blinking.js.map +1 -1
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/alert-severity.spec.ts +81 -2
- package/src/alert-severity.ts +74 -0
- package/src/components/alert-button/alert-button.css +7 -196
- package/src/components/alert-button/alert-button.spec.ts +119 -68
- package/src/components/alert-button/alert-button.stories.ts +24 -0
- package/src/components/alert-button/alert-button.ts +49 -211
- package/src/components/alert-button-item/alert-button-item.css +242 -0
- package/src/components/alert-button-item/alert-button-item.spec.ts +299 -0
- package/src/components/alert-button-item/alert-button-item.stories.ts +178 -0
- package/src/components/alert-button-item/alert-button-item.ts +289 -0
- package/src/components/alert-counter-item/alert-counter-item.css +15 -0
- package/src/components/alert-counter-item/alert-counter-item.spec.ts +91 -0
- package/src/components/alert-counter-item/alert-counter-item.stories.ts +66 -0
- package/src/components/alert-counter-item/alert-counter-item.ts +128 -0
- package/src/components/alert-frame/alert-frame.css +23 -27
- package/src/components/alert-frame/alert-frame.spec.ts +19 -1
- package/src/components/alert-frame/alert-frame.stories.ts +47 -0
- package/src/components/alert-frame/alert-frame.ts +16 -17
- package/src/components/tree-navigation-item/tree-navigation-item.ts +10 -60
- package/src/generated/locales/es-419.ts +11 -3
- package/src/generated/locales/fi-FI.ts +8 -0
- package/src/navigation-instruments/readout/readout.css +4 -29
- package/src/navigation-instruments/readout/readout.ts +1 -9
- package/src/navigation-instruments/readout-list-item/readout-list-item.css +5 -33
- package/src/navigation-instruments/readout-list-item/readout-list-item.ts +1 -9
- package/src/palettes/blinking.spec.ts +38 -23
- package/src/palettes/blinking.ts +12 -4
- package/src/types.ts +16 -0
package/custom-elements.json
CHANGED
|
@@ -4754,16 +4754,45 @@
|
|
|
4754
4754
|
},
|
|
4755
4755
|
{
|
|
4756
4756
|
"kind": "javascript-module",
|
|
4757
|
-
"path": "dist/components/alert-button/alert-button.js",
|
|
4757
|
+
"path": "dist/components/alert-button-item/alert-button-item.js",
|
|
4758
4758
|
"declarations": [
|
|
4759
4759
|
{
|
|
4760
4760
|
"kind": "variable",
|
|
4761
4761
|
"name": "ObcAlertButtonType"
|
|
4762
4762
|
},
|
|
4763
|
+
{
|
|
4764
|
+
"kind": "variable",
|
|
4765
|
+
"name": "ObcAlertButtonItem",
|
|
4766
|
+
"default": "class extends LitElement { constructor() { super(...arguments); this.type = \"normal\"; this.nAlerts = 0; this.counter = false; this.globalCounter = false; this.counts = {}; this.shelvedCount = 0; this.blinking = false; this.flashingSpeed = FlashingSpeed.Default; this.fillHeight = false; this.ariaLabel = null; this.flashing = new FlashingController( this, () => this.resolvedFlashingSpeed ); } get resolvedFlashingSpeed() { if (this.globalCounter || !this.blinking || this.nAlerts <= 0 || this.alertType === void 0) { return FlashingSpeed.Fixed; } return resolveFlashingSpeed( this.flashingSpeed, this.alertType, AlertFlashPhase.Active ); } get accessibleName() { if (this.ariaLabel) { return this.ariaLabel; } const parts = [msg(\"Alerts\")]; if (this.nAlerts > 0) { parts.push(String(this.nAlerts)); } if (this.globalCounter) { const breakdown = alertCountsLabel(this.counts, this.shelvedCount); if (breakdown) { parts.push(breakdown); } } else if (this.nAlerts > 0 && this.alertType) { parts.push(alertTypeLabel(this.alertType)); } return parts.join(\", \"); } renderTwotoneIcon() { const twotone = this.alertType ? getAlertTwotoneComponent(this.alertType) : AlertTwotoneComponent.Caution; switch (twotone) { case AlertTwotoneComponent.Critical: return html`<obi-alerts-critical-twotone useCssColor class=\"icon\" ></obi-alerts-critical-twotone>`; case AlertTwotoneComponent.Diagnostic: return html`<obi-alerts-diagnostic-twotone useCssColor class=\"icon\" ></obi-alerts-diagnostic-twotone>`; case AlertTwotoneComponent.Warning: return html`<obi-alerts-warning-twotone useCssColor class=\"icon\" ></obi-alerts-warning-twotone>`; case AlertTwotoneComponent.Caution: return html`<obi-alerts-caution-twotone useCssColor class=\"icon\" ></obi-alerts-caution-twotone>`; default: return html`<obi-alerts-alarm-twotone useCssColor class=\"icon\" ></obi-alerts-alarm-twotone>`; } } /** The blink layer shows the opposite of the steady layer in each flash. */ renderIcon(styleType, blinkLayer) { if (this.nAlerts === 0) { return html`<obi-alerts class=\"icon\"></obi-alerts>`; } const enhanced = styleType === \"enhanced\"; if (blinkLayer) { return enhanced ? this.renderTwotoneIcon() : html`<obi-alerts class=\"icon\"></obi-alerts>`; } return enhanced || this.globalCounter ? html`<obi-alerts-active class=\"icon\"></obi-alerts-active>` : this.renderTwotoneIcon(); } renderCount() { return html`<span class=\"count\">${this.nAlerts}</span>`; } renderCounts() { if (rankAlertCounts(this.counts).length === 0 && this.shelvedCount <= 0) { return nothing; } return html`<obc-alert-counter-item .type=${ObcAlertCounterItemType.Badges} .counts=${this.counts} .shelvedCount=${this.shelvedCount} ></obc-alert-counter-item>`; } render() { const styleType = this.globalCounter ? \"normal\" : this.type; const showCount = this.nAlerts > 0 && (this.globalCounter || this.counter && styleType !== \"flat\"); const tempo = this.resolvedFlashingSpeed; return html` <button class=${classMap({ \"alert-button\": true, [`type-${styleType}`]: true, [`alert-type-${this.alertType ?? \"none\"}`]: true, counter: showCount && !this.globalCounter, \"global-counter\": this.globalCounter, \"fill-height\": this.fillHeight, [`flash-${tempo}`]: true })} aria-label=${this.accessibleName} > ${tempo === FlashingSpeed.Fixed ? nothing : html`<div class=\"blink\" aria-hidden=\"true\"> <div class=\"content\"> ${this.renderIcon(styleType, true)} ${showCount ? this.renderCount() : nothing} </div> </div>`} <div class=\"visible-wrapper\"> ${this.globalCounter ? this.renderCounts() : nothing} <div class=\"content\"> ${this.renderIcon(styleType, false)} ${showCount ? this.renderCount() : nothing} </div> </div> </button> `; } }"
|
|
4767
|
+
}
|
|
4768
|
+
],
|
|
4769
|
+
"exports": [
|
|
4770
|
+
{
|
|
4771
|
+
"kind": "js",
|
|
4772
|
+
"name": "ObcAlertButtonItem",
|
|
4773
|
+
"declaration": {
|
|
4774
|
+
"name": "ObcAlertButtonItem",
|
|
4775
|
+
"module": "dist/components/alert-button-item/alert-button-item.js"
|
|
4776
|
+
}
|
|
4777
|
+
},
|
|
4778
|
+
{
|
|
4779
|
+
"kind": "js",
|
|
4780
|
+
"name": "ObcAlertButtonType",
|
|
4781
|
+
"declaration": {
|
|
4782
|
+
"name": "ObcAlertButtonType",
|
|
4783
|
+
"module": "dist/components/alert-button-item/alert-button-item.js"
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
]
|
|
4787
|
+
},
|
|
4788
|
+
{
|
|
4789
|
+
"kind": "javascript-module",
|
|
4790
|
+
"path": "dist/components/alert-button/alert-button.js",
|
|
4791
|
+
"declarations": [
|
|
4763
4792
|
{
|
|
4764
4793
|
"kind": "variable",
|
|
4765
4794
|
"name": "ObcAlertButton",
|
|
4766
|
-
"default": "class extends LitElement { constructor() { super(...arguments); this.nAlerts = 0; this.type =
|
|
4795
|
+
"default": "class extends LitElement { constructor() { super(...arguments); this.nAlerts = 0; this.type = ObcAlertButtonType.Normal; this.large = false; this.showSilenceButton = false; this.silenceButtonDisabled = false; this.counter = false; this.globalCounter = false; this.counts = {}; this.shelvedCount = 0; this.blinking = false; this.flashingSpeed = FlashingSpeed.Default; this.flatMaxBreakpointPx = 0; this.silenceButtonMinBreakpointPx = 0; this.width = window.innerWidth; this.resizeListener = () => { this.width = window.innerWidth; }; } connectedCallback() { super.connectedCallback(); window.addEventListener(\"resize\", this.resizeListener); } disconnectedCallback() { window.removeEventListener(\"resize\", this.resizeListener); super.disconnectedCallback(); } get activeType() { if (this.type === ObcAlertButtonType.Flat) { return ObcAlertButtonType.Flat; } if (this.width < this.flatMaxBreakpointPx) { return ObcAlertButtonType.Flat; } return this.type; } get showSilenceButtonDynamic() { return this.showSilenceButton && this.width >= this.silenceButtonMinBreakpointPx && this.activeType !== ObcAlertButtonType.Flat; } render() { const showSilence = this.showSilenceButtonDynamic; return html` <div class=${classMap({ wrapper: true, [`type-${this.activeType}`]: true, large: this.large })} > <obc-alert-button-item .type=${this.activeType} .alertType=${this.alertType} .nAlerts=${this.nAlerts} .counter=${this.counter} .globalCounter=${this.globalCounter && this.activeType !== ObcAlertButtonType.Flat} .counts=${this.counts} .shelvedCount=${this.shelvedCount} .blinking=${this.blinking} .flashingSpeed=${this.flashingSpeed} .fillHeight=${this.large} ?data-group-item-not-last=${showSilence} @click=${() => this.dispatchEvent(new CustomEvent(\"click-alert\"))} ></obc-alert-button-item> ${showSilence ? html` <button class=\"silence-button\" aria-label=${msg(\"Silence\")} @click=${() => this.dispatchEvent(new CustomEvent(\"click-silence\"))} ?disabled=${this.silenceButtonDisabled} > <div class=\"visible-wrapper\"> <obi-silence-iec class=\"icon\"></obi-silence-iec> </div> </button> ` : nothing} </div> `; } }"
|
|
4767
4796
|
}
|
|
4768
4797
|
],
|
|
4769
4798
|
"exports": [
|
|
@@ -4785,6 +4814,39 @@
|
|
|
4785
4814
|
}
|
|
4786
4815
|
]
|
|
4787
4816
|
},
|
|
4817
|
+
{
|
|
4818
|
+
"kind": "javascript-module",
|
|
4819
|
+
"path": "dist/components/alert-counter-item/alert-counter-item.js",
|
|
4820
|
+
"declarations": [
|
|
4821
|
+
{
|
|
4822
|
+
"kind": "variable",
|
|
4823
|
+
"name": "ObcAlertCounterItemType"
|
|
4824
|
+
},
|
|
4825
|
+
{
|
|
4826
|
+
"kind": "variable",
|
|
4827
|
+
"name": "ObcAlertCounterItem",
|
|
4828
|
+
"default": "class extends LitElement { constructor() { super(...arguments); this.type = \"badges\"; this.counts = {}; this.shelvedCount = 0; } render() { const ranked = rankAlertCounts(this.counts); if (ranked.length === 0 && this.shelvedCount <= 0) { return nothing; } const alertLevel = this.type === \"alert-level\"; const variant = alertLevel ? BadgeVariant.flat : BadgeVariant.default; return html` <div class=\"row\" role=\"img\" aria-label=${alertCountsLabel(this.counts, this.shelvedCount)} > ${repeat( ranked, (entry) => entry.type, (entry) => html`<obc-badge size=${BadgeSize.large} type=${entry.type} .variant=${variant} .showIcon=${alertLevel} .number=${entry.count} ></obc-badge>` )} ${this.shelvedCount > 0 ? html`<obc-badge size=${BadgeSize.large} type=${BadgeType.regular} .variant=${variant} .showIcon=${alertLevel} .number=${this.shelvedCount} > ${alertLevel ? html`<obi-alerts-shelf slot=\"badge-icon\"></obi-alerts-shelf>` : nothing} </obc-badge>` : nothing} </div> `; } }"
|
|
4829
|
+
}
|
|
4830
|
+
],
|
|
4831
|
+
"exports": [
|
|
4832
|
+
{
|
|
4833
|
+
"kind": "js",
|
|
4834
|
+
"name": "ObcAlertCounterItem",
|
|
4835
|
+
"declaration": {
|
|
4836
|
+
"name": "ObcAlertCounterItem",
|
|
4837
|
+
"module": "dist/components/alert-counter-item/alert-counter-item.js"
|
|
4838
|
+
}
|
|
4839
|
+
},
|
|
4840
|
+
{
|
|
4841
|
+
"kind": "js",
|
|
4842
|
+
"name": "ObcAlertCounterItemType",
|
|
4843
|
+
"declaration": {
|
|
4844
|
+
"name": "ObcAlertCounterItemType",
|
|
4845
|
+
"module": "dist/components/alert-counter-item/alert-counter-item.js"
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
]
|
|
4849
|
+
},
|
|
4788
4850
|
{
|
|
4789
4851
|
"kind": "javascript-module",
|
|
4790
4852
|
"path": "dist/components/alert-floating-item/alert-floating-item.js",
|
|
@@ -4829,7 +4891,7 @@
|
|
|
4829
4891
|
{
|
|
4830
4892
|
"kind": "variable",
|
|
4831
4893
|
"name": "ObcAlertFrame",
|
|
4832
|
-
"default": "class extends LitElement { constructor() { super(...arguments); this.type = \"small-side-flip\"; this.thickness = \"small\"; this.status = AlertType.Alarm; this.mode = \"acked-active\"; this.flashingSpeed = FlashingSpeed.Default; this.flashEffect = \"outline\"; this.flashing = new FlashingController( this, () => this.resolvedFlashingSpeed ); this.measureDash = () => { const wrapper = this.wrapper; if (!wrapper) { return; } const style = getComputedStyle(wrapper); const px = (value) => parseFloat(value) || 0; const box = { width: wrapper.offsetWidth, height: wrapper.offsetHeight, thickness: px(style.getPropertyValue(\"--thickness\")), radii: [ px(style.borderTopLeftRadius), px(style.borderTopRightRadius), px(style.borderBottomRightRadius), px(style.borderBottomLeftRadius) ] }; if (!sameDashBox(box, this.dashBox)) { this.dashBox = box; } }; this.wrapContent = false; this.fullWidth = false; this.sharpEdgeTopLeft = false; this.sharpEdgeTopRight = false; this.sharpEdgeBottomLeft = false; this.sharpEdgeBottomRight = false; this.textSize = \"regular\"; this.showIcon = false; this.showAlertCategoryIcon = true; } connectedCallback() { super.connectedCallback(); if (this.hasUpdated) { this.requestUpdate(); } } updated() { const wantsDash = this.mode === \"unacked-rectified\" && this.isConnected; if (wantsDash && !this.dashObserver && this.wrapper) { this.dashObserver = new ResizeObserver(this.measureDash); this.dashObserver.observe(this.wrapper); } if (wantsDash) { this.measureDash(); } if (!wantsDash && this.dashObserver) { this.dashObserver.disconnect(); this.dashObserver = void 0; this.dashBox = void 0; } } disconnectedCallback() { super.disconnectedCallback(); this.dashObserver?.disconnect(); this.dashObserver = void 0; } renderDash() { const box = this.dashBox; if (this.mode !== \"unacked-rectified\" || !box) { return nothing; } const pad = box.thickness + DASH_FLASH_GROWTH_PX; const centreline = { x: pad
|
|
4894
|
+
"default": "class extends LitElement { constructor() { super(...arguments); this.type = \"small-side-flip\"; this.thickness = \"small\"; this.status = AlertType.Alarm; this.mode = \"acked-active\"; this.flashingSpeed = FlashingSpeed.Default; this.flashEffect = \"outline\"; this.flashing = new FlashingController( this, () => this.resolvedFlashingSpeed ); this.measureDash = () => { const wrapper = this.wrapper; if (!wrapper) { return; } const style = getComputedStyle(wrapper); const px = (value) => parseFloat(value) || 0; const box = { width: wrapper.offsetWidth, height: wrapper.offsetHeight, thickness: px(style.getPropertyValue(\"--thickness\")), radii: [ px(style.borderTopLeftRadius), px(style.borderTopRightRadius), px(style.borderBottomRightRadius), px(style.borderBottomLeftRadius) ] }; if (!sameDashBox(box, this.dashBox)) { this.dashBox = box; } }; this.wrapContent = false; this.fullWidth = false; this.sharpEdgeTopLeft = false; this.sharpEdgeTopRight = false; this.sharpEdgeBottomLeft = false; this.sharpEdgeBottomRight = false; this.textSize = \"regular\"; this.showIcon = false; this.showAlertCategoryIcon = true; } connectedCallback() { super.connectedCallback(); if (this.hasUpdated) { this.requestUpdate(); } } updated() { const wantsDash = this.mode === \"unacked-rectified\" && this.isConnected; if (wantsDash && !this.dashObserver && this.wrapper) { this.dashObserver = new ResizeObserver(this.measureDash); this.dashObserver.observe(this.wrapper); } if (wantsDash) { this.measureDash(); } if (!wantsDash && this.dashObserver) { this.dashObserver.disconnect(); this.dashObserver = void 0; this.dashBox = void 0; } } disconnectedCallback() { super.disconnectedCallback(); this.dashObserver?.disconnect(); this.dashObserver = void 0; } renderDash() { const box = this.dashBox; if (this.mode !== \"unacked-rectified\" || !box) { return nothing; } const pad = (box.thickness + DASH_FLASH_GROWTH_PX) / 2; const centreline = { x: pad, y: pad, width: box.width, height: box.height, radii: box.radii }; const width = box.width + 2 * pad; const height = box.height + 2 * pad; return html`<svg class=\"dash\" aria-hidden=\"true\" width=${width} height=${height} viewBox=\"0 0 ${width} ${height}\" style=\"--dash-pad: ${pad}px\" > <path d=${roundedRectPath(centreline)}></path> </svg>`; } get resolvedFlashingSpeed() { switch (this.mode) { case \"unacked-active\": return resolveFlashingSpeed( this.flashingSpeed, this.status, AlertFlashPhase.Active ); case \"unacked-rectified\": return resolveFlashingSpeed( this.flashingSpeed, this.status, AlertFlashPhase.Rectified ); default: return FlashingSpeed.Fixed; } } render() { return html` <div class=${classMap({ wrapper: true, \"wrap-content\": this.wrapContent, \"full-width\": this.fullWidth, [\"thickness-\" + this.thickness]: true, [this.type]: true, [this.status]: true, [\"text-size-\" + this.textSize]: true, \"sharp-edge-top-left\": this.sharpEdgeTopLeft, \"sharp-edge-top-right\": this.sharpEdgeTopRight, \"sharp-edge-bottom-left\": this.sharpEdgeBottomLeft, \"sharp-edge-bottom-right\": this.sharpEdgeBottomRight, [this.mode]: true, [\"flash-\" + this.resolvedFlashingSpeed]: true, [\"flash-effect-\" + this.flashEffect]: true })} > <slot></slot> ${this.renderDash()} ${this.flap()} </div> `; } flap() { if (this.type === \"regular\") { return nothing; } if (this.type === \"small-side-flip\" && !this.showAlertCategoryIcon) { return nothing; } if (this.type === \"large-side-flip\" && !this.showIcon && !this.showAlertCategoryIcon) { return nothing; } let icon; if (!this.showAlertCategoryIcon) { icon = nothing; } else { icon = this.renderBadgeIcon(); } if (this.type === \"small-side-flip\") { return html`<div class=\"flap small\"> ${icon} <div class=\"mask up\"></div> </div>`; } if (this.type === \"large-side-flip\") { return html`<div class=\"flap large\"> ${icon} ${this.showIcon ? html`<div class=\"icon\"><slot name=\"icon\"></slot></div>` : nothing} <div class=\"mask up\"></div> <div class=\"mask down\"></div> </div>`; } if (this.type === \"bottom-flip\" || this.type === \"top-flip\") { return html`<div class=\"flap ${this.type === \"bottom-flip\" ? \"bottom\" : \"top\"}\" > ${icon} ${this.showIcon ? html`<div class=\"icon\"><slot name=\"icon\"></slot></div>` : nothing} <div class=\"label\"><slot name=\"label\"></slot></div> <div class=\"spacer\"></div> <div class=\"timer\"><slot name=\"timer\"></slot></div> <div class=\"mask right\"></div> <div class=\"mask left\"></div> </div>`; } console.error(\"Unknown type of alert frame:\", this.type); return nothing; } renderBadgeIcon() { switch (getAlertBadgeComponent(this.status)) { case AlertBadgeComponent.Critical: return html`<obi-critical-badge class=\"icon badge\" ></obi-critical-badge>`; case AlertBadgeComponent.Warning: return html`<obi-warning-badge class=\"icon badge\"></obi-warning-badge>`; case AlertBadgeComponent.Caution: return html`<obi-caution-badge class=\"icon badge\"></obi-caution-badge>`; case AlertBadgeComponent.Diagnostic: return html`<obi-diagnostic-badge class=\"icon badge\" ></obi-diagnostic-badge>`; default: return html`<obi-alarm-badge class=\"icon badge\"></obi-alarm-badge>`; } } }"
|
|
4833
4895
|
}
|
|
4834
4896
|
],
|
|
4835
4897
|
"exports": [
|
|
@@ -8380,7 +8442,7 @@
|
|
|
8380
8442
|
{
|
|
8381
8443
|
"kind": "variable",
|
|
8382
8444
|
"name": "ObcTreeNavigationItem",
|
|
8383
|
-
"default": "class extends LitElement { constructor() { super(...arguments); this.label = \"List item\"; this.branches = []; this.expandable = false; this.expanded = false; this.checked = false; this.disabled = false; this.focusable = true; this.hasLeadingIcon = true; this.terminalType = \"regular\"; } /** Focuses the row's interactive wrapper (the host itself is not focusable). */ focus(options) { this.wrapperElement?.focus(options); } /**
|
|
8445
|
+
"default": "class extends LitElement { constructor() { super(...arguments); this.label = \"List item\"; this.branches = []; this.expandable = false; this.expanded = false; this.checked = false; this.disabled = false; this.focusable = true; this.hasLeadingIcon = true; this.terminalType = \"regular\"; } /** Focuses the row's interactive wrapper (the host itself is not focusable). */ focus(options) { this.wrapperElement?.focus(options); } /** The badge(s) to render from `alerts`, ranked by `rankAlertCounts`. */ get alertBadges() { return this.alerts ? rankAlertCounts(this.alerts, this.alerts.combine) : []; } /** A root-level row has no ancestor columns, so it draws no connector lines. */ get isRoot() { return this.branches.length === 0; } /** * All ancestor columns are blank spacers — the row is indented but draws no * guide lines, so the terminal connector and dropdown are suppressed too. */ get isBlankAncestry() { return this.branches.length > 0 && this.branches.every( (b) => b === \"blank\" /* blank */ ); } activate() { if (this.disabled) return; if (this.expandable) { this.dispatchEvent( new CustomEvent(\"expand-toggle\", { detail: !this.expanded }) ); } if (this.checked) return; this.dispatchEvent(new CustomEvent(\"click\")); if (this.href !== void 0) { window.location.href = this.href; } } handleKeydown(event) { if (this.disabled) return; if (event.key !== \"Enter\" && event.key !== \" \") return; event.preventDefault(); this.activate(); } renderBranch(type) { const hasVertical = type === \"straight\" || type === \"intersection\" || type === \"corner\"; const hasHorizontal = type === \"intersection\" || type === \"corner\"; return html`<div class=${classMap({ branch: true, [\"branch-\" + type]: true })} > ${hasVertical ? html`<div class=\"branch-vertical\"></div>` : nothing} ${hasHorizontal ? html`<div class=\"branch-horizontal\"></div>` : nothing} ${type === \"corner\" ? html`<div class=\"branch-elbow\"></div>` : nothing} </div>`; } renderTerminalHeader() { if (this.terminalType === \"aggregated-header\") { return html`<div class=\"terminal-header\" aria-hidden=\"true\"> <obi-alert-header-aggregated-iec></obi-alert-header-aggregated-iec> </div>`; } if (this.terminalType === \"group-header\") { return html`<div class=\"terminal-header\" aria-hidden=\"true\"> <obi-alert-header-group-iec></obi-alert-header-group-iec> </div>`; } return nothing; } render() { return html` <div class=${classMap({ wrapper: true, checked: this.checked, disabled: this.disabled, \"has-icon\": this.hasLeadingIcon })} role=\"treeitem\" aria-expanded=${ifDefined(this.expandable ? this.expanded : void 0)} aria-current=${ifDefined(this.checked ? \"page\" : void 0)} aria-selected=${ifDefined(this.checked ? \"true\" : void 0)} aria-disabled=${ifDefined(this.disabled ? \"true\" : void 0)} tabindex=${this.disabled ? -1 : this.focusable ? 0 : -1} @click=${this.activate} @keydown=${this.handleKeydown} > <div class=\"visible-wrapper\"> <div class=\"tree-node-row\"> ${this.branches.map((branch) => this.renderBranch(branch))} <div class=\"terminal\"> ${this.isRoot || this.isBlankAncestry ? nothing : html`<div class=\"terminal-connector\"></div>`} ${!this.isRoot && !this.isBlankAncestry && this.expandable && this.expanded ? html`<div class=\"terminal-dropdown\"></div>` : nothing} ${this.expandable ? html`<div class=\"chevron\" aria-hidden=\"true\"> <obi-chevron-right-google></obi-chevron-right-google> </div>` : nothing} ${this.renderTerminalHeader()} </div> </div> <div class=\"label-container\"> ${this.hasLeadingIcon ? html`<div class=\"leading-icon\"> <slot name=\"icon\"></slot> </div>` : nothing} <span part=\"label\" class=\"label\">${this.label}</span> </div> ${this.alertBadges.length > 0 ? html`<div class=\"alert-badges\"> ${this.alertBadges.map( (badge) => html`<obc-badge class=\"alert-badge\" .type=${badge.type} .number=${badge.count} ></obc-badge>` )} </div>` : nothing} </div> </div> `; } }"
|
|
8384
8446
|
}
|
|
8385
8447
|
],
|
|
8386
8448
|
"exports": [
|
|
@@ -10160,7 +10222,7 @@
|
|
|
10160
10222
|
{
|
|
10161
10223
|
"kind": "variable",
|
|
10162
10224
|
"name": "ObcReadoutListItem",
|
|
10163
|
-
"default": "class extends LitElement { constructor() { super(...arguments); this.hasValue = true; this.value = null; this.valueType = ReadoutValueType.number; this.off = false; this.offText = \"OFF\"; this.hasSetpoint = false; this.hasAdvice = false; this.clickable = false; this.hasLeadingIcon = false; this.hasDegree = false; this.hasDegreeSpacer = false; this.fractionDigits = 0; this.maxDigits = 0; this.alert = false; this.showDebugOverlay = false; this.deferredSetpointHidePhase = ReadoutBlockHidePhase.none; this.hasCompletedFirstUpdate = false; } get resolvedSize() { return this.size ?? ReadoutListItemSize.small; } get resolvedStacking() { return this.stacking ?? \"trailing-unit\"; } /** Both leading-src stackings move the source into the label stack. */ get hasLeadingSrc() { const stacking = this.resolvedStacking; return stacking === \"leading-src\" || stacking === \"leading-src-inline\"; } get resolvedPriority() { return this.priority ?? \"regular\"; } /** * Whether a unit box follows the value: a unit, or a reserver alone — the * blank reserved column keeps a unit-less row aligned with its neighbours. */ get hasTrailingUnitBox() { return this.resolvedStacking !== \"leading-unit\" && (Boolean(this.unit) || Boolean(this.unitOptions?.spaceReserver)); } get resolvedFractionDigits() { return this.fractionDigits ?? 0; } get resolvedMaxDigits() { return resolveReadoutDigitCount(this.maxDigits); } /** * Whether a digit knob failed to arrive (`NaN` / `null` / `undefined`). * The raw knobs are forwarded to the blocks, which render the reading as * the unavailable dash (see `obc-readout-block.digitCountsMissing`); this * getter only keeps the setpoint comparison in agreement with what is * displayed. */ get digitCountsMissing() { return isReadoutDigitCountMissing(this.fractionDigits) || isReadoutDigitCountMissing(this.maxDigits); } get resolvedClickable() { const clickable = this.clickable; if (!clickable) { return false; } if (clickable === true) { return { border: \"squared\" /* squared */ }; } return { border: clickable.border ?? \"squared\" /* squared */ }; } get isAtSetpoint() { if (!this.hasSetpoint) { return false; } if (this.digitCountsMissing) { return false; } return isDisplayedAtSetpoint( resolveReadoutNumericValue(this.value, this.valueType) ?? null, this.setpoint, this.numericFormatOptions(this.resolvedMaxDigits) ); } get resolvedSetpointInteraction() { return this.setpointOptions?.interaction ?? \"always-visible\"; } get isFlipFlop() { return this.resolvedSetpointInteraction === \"flip-flop\"; } get isEqualSize() { return this.resolvedSetpointInteraction === \"equal-size\"; } get isPopUp() { return this.resolvedSetpointInteraction === \"pop-up\"; } get setpointTouching() { return this.setpointOptions?.touching ?? false; } /** * The setpoint is rendered \"emphasised\" (primary size + SemiBold weight) when * it is the focus of attention: while actively adjusting (`touching`), or while * a flip-flop has the value away from the setpoint. Otherwise it is a secondary * (smaller, regular-weight) reference next to the value. */ get isSetpointEmphasized() { if (!this.hasSetpoint) { return false; } if (this.setpointTouching) { return true; } return this.isFlipFlop && !this.isAtSetpoint; } /** * The row's enhanced (in-command) colour state, applied uniformly to BOTH the * value and the setpoint — they are always either both neutral or both enhanced * (never a blue setpoint next to a grey value). Driven by `priority` only; * `valueOptions.weight` changes weight, not colour. */ get rowEnhanced() { return this.resolvedPriority === \"enhanced\"; } /** Primary value-typography size for the current density tier. */ get primarySize() { return readoutPrimarySize(this.resolvedSize); } /** Secondary (de-emphasised) value-typography size for the current density tier. */ get secondarySize() { return readoutSecondarySize(this.resolvedSize); } get valueSize() { return readoutValueSize({ primary: this.primarySize, secondary: this.secondarySize, setpointEmphasized: this.isSetpointEmphasized, equalSize: this.isEqualSize }); } get setpointSize() { return readoutSetpointSize({ primary: this.primarySize, secondary: this.secondarySize, emphasized: this.isSetpointEmphasized, equalSize: this.isEqualSize }); } /** Value font weight passes straight to obc-textbox; regular when unset. Colour is separate. */ get valueWeight() { return this.valueOptions?.weight ?? ObcTextboxFontWeight.regular; } /** Label size: `xs` for dense list rows unless overridden (see {@link ReadoutLabelOptions}). */ get labelSize() { return this.labelOptions?.size ?? ObcTextboxSize.xs; } /** * Label weight: SemiBold only for enhanced (in-command) rows, regular * otherwise — the designer reduced the amount of semibold in the interface * (Figma 6.1 review). */ get labelWeight() { return this.rowEnhanced ? ObcTextboxFontWeight.semibold : ObcTextboxFontWeight.regular; } /** Setpoint is SemiBold only while emphasised, otherwise regular weight. */ get setpointWeight() { return readoutSetpointWeight(this.isSetpointEmphasized); } numericFormatOptions(maxDigits) { return readoutNumericFormatOptions(maxDigits, this.resolvedFractionDigits); } /** classMap fragment for a block / source carrying per-block data quality. */ dataQualityClasses(dataQuality) { return readoutDataQualityClasses(dataQuality); } /** * Forward the matching list-item icon slot into the block's single `icon` * slot. The variant's default marker lives in `obc-readout-block` and shows * when nothing is assigned here (an empty forwarded slot flattens to nothing). */ renderForwardedIcon(variant) { if (variant === ReadoutBlockVariant.setpoint) { return html`<slot name=\"setpoint-icon\" slot=\"icon\"></slot>`; } if (variant === ReadoutBlockVariant.advice) { return html`<slot name=\"advice-icon\" slot=\"icon\"></slot>`; } return html`<slot name=\"value-icon\" slot=\"icon\"></slot>`; } renderBlock(config) { return html` <obc-readout-block exportparts=\"block, block-content, block-text, block-icon, degree\" .variant=${config.variant} .value=${config.value ?? null} .valueType=${config.valueType ?? ReadoutValueType.number} .size=${this.resolvedSize} .valueSize=${config.valueSize} .enhanced=${config.enhanced} .weight=${config.weight} .hasDegree=${config.hasDegree ?? false} .hasIcon=${config.hasIcon ?? false} .fractionDigits=${this.fractionDigits} .maxDigits=${this.maxDigits} .hintedZeros=${config.hintedZeros} .hasSignSpacer=${config.hasSignSpacer ?? false} .spaceReserver=${config.spaceReserver} .off=${config.off ?? false} .offText=${this.offText} .touching=${config.touching ?? false} .hidePhase=${config.hidePhase ?? ReadoutBlockHidePhase.none} .dataQuality=${config.dataQuality} .alert=${config.alert ?? false} .category=${config.category ?? ReadoutAdviceCategory.regular} .active=${config.active ?? false} > ${this.renderForwardedIcon(config.variant)} </obc-readout-block> `; } /** * A cap-height `°` column whose width scales with the value size. Used after * the value (as the value↔unit boundary, via {@link renderValueUnitGap}) and * inside the setpoint / advice blocks. `inherit` makes the glyph take the * surrounding block's colour (setpoint/advice); otherwise it uses the value * colour, optionally `enhanced`. */ renderDegreeGlyph(size, opts = {}) { return html` <span class=${classMap({ \"degree-column\": true, [`degree-${size}`]: true, \"tone-enhanced\": !opts.inherit && Boolean(opts.enhanced), \"degree-inherit\": Boolean(opts.inherit) })} part=\"degree\" > <obc-textbox class=\"degree-glyph\" .size=${size} alignment=\"center\" >°</obc-textbox > </span> `; } /** * The gap rendered between the value digits and the unit. * * - `hasDegree`: a cap-height `°` column whose width scales with the value * size (the `°` replaces the default gap). * - otherwise: the default 2px gap (only when a trailing unit follows). * * `hasDegreeSpacer` deliberately does NOT add anything here — it keeps the 2px * gap and instead widens the unit column via {@link renderDegreeSpacer} (a * spacer AFTER the unit). That way a non-degree row's value digits stay * aligned with degree rows (degree column width = spacer width + 2px gap) * while its unit shifts left. Mirrors Figma `1:2920` (spacer) / `1:2970` * (degree). * * TODO(designer): cross-size alignment is deferred. Degree rows of different * value sizes have different `°` column widths (6/8/12px), so their value digit * edges stagger by `degree-width`. For degree rows of mixed sizes you cannot * align the value digit edges AND keep the unit column aligned — resolving it * needs a design decision (a constant degree reserve, which widens the smaller * rows' `°`, OR pinning the value edge and letting the unit column stagger). */ renderValueUnitGap() { if (!this.hasValue) { return nothing; } if ((this.hasDegree ?? false) && !this.off) { return this.renderDegreeGlyph(this.valueSize, { enhanced: this.rowEnhanced }); } if (this.hasTrailingUnitBox) { return html`<span class=\"value-unit-gap\" aria-hidden=\"true\"></span>`; } return nothing; } /** * A spacer rendered AFTER the unit when `hasDegreeSpacer` is set on a * non-degree row. Its width (`degree-compensation-padding`) = the degree * column width minus the 2px gap, so the row's value digits align with degree * rows in the same column while its unit shifts left. See * {@link renderValueUnitGap}. */ renderDegreeSpacer() { const hasDegree = this.hasDegree ?? false; const hasDegreeSpacer = this.hasDegreeSpacer ?? false; if (hasDegree || !hasDegreeSpacer) { return nothing; } return html`<span class=\"degree-spacer\" part=\"degree-spacer\" aria-hidden=\"true\" ></span>`; } renderTextbox(role, text, reserver, state2) { const weight = role === \"label\" ? this.labelWeight : ObcTextboxFontWeight.regular; const size = role === \"label\" ? this.labelSize : ObcTextboxSize.xs; const box = html` <obc-textbox class=${classMap({ [role]: true, ...this.dataQualityClasses(state2?.dataQuality) })} part=${role} .size=${size} .fontWeight=${weight} alignment=\"left\" > ${text} ${reserver ? html`<span slot=\"length\">${reserver}</span>` : nothing} </obc-textbox> `; return wrapWithAlertFrame(state2?.alert ?? false, box); } renderValueCluster() { const popUpAtSetpoint = this.isPopUp && this.isAtSetpoint && !this.setpointTouching; const setpointHidePhase = resolveSetpointHidePhase( popUpAtSetpoint, this.deferredSetpointHidePhase ); return html` <div class=\"value-cluster\" part=\"value-cluster\"> ${this.hasAdvice ? this.renderBlock({ variant: ReadoutBlockVariant.advice, value: this.advice, valueSize: this.secondarySize, enhanced: false, weight: ObcTextboxFontWeight.regular, hintedZeros: this.adviceOptions?.hintedZeros ?? false, hasSignSpacer: this.adviceOptions?.hasSignSpacer ?? false, spaceReserver: this.adviceOptions?.spaceReserver, hasDegree: this.hasDegree ?? false, dataQuality: this.adviceOptions?.dataQuality, alert: this.adviceOptions?.alert, category: this.adviceOptions?.category, active: this.adviceOptions?.active }) : nothing} ${this.hasSetpoint ? this.renderBlock({ variant: ReadoutBlockVariant.setpoint, value: this.setpoint, valueSize: this.setpointSize, // Value and setpoint share the enhanced colour state (both neutral // or both enhanced); the setpoint is bold only while emphasised. enhanced: this.rowEnhanced, weight: this.setpointWeight, hintedZeros: this.setpointOptions?.hintedZeros ?? false, hasSignSpacer: this.setpointOptions?.hasSignSpacer ?? false, spaceReserver: this.setpointOptions?.spaceReserver, hasDegree: this.hasDegree ?? false, touching: this.setpointTouching, hidePhase: setpointHidePhase, dataQuality: this.setpointOptions?.dataQuality, alert: this.setpointOptions?.alert }) : nothing} ${this.renderValueReading()} </div> `; } /** * The value reading: the value block, its degree column and the trailing unit * grouped in one relatively-positioned wrapper so the value alert frame AND the * value data-quality chip can wrap value + degree + unit together (Figma * 58:10120). * * Moving the degree / unit into this wrapper (the new last child of * `.value-cluster`) preserves every existing gap, so the underlying content * stays column-aligned with or without the frame / chip. Both the value alert * (overlay) and the value data-quality chip are applied here — NOT inside * `obc-readout-block` — so they extend over the unit; setpoint/advice keep their * own block-level frame and chip. The chip uses `outline` (not `border`), so it * never shifts the value's width / column alignment. */ renderValueReading() { return html` <div class=${classMap({ \"value-reading\": true, ...this.dataQualityClasses(this.valueOptions?.dataQuality) })} part=\"value-reading\" > ${this.hasValue ? this.renderBlock({ variant: ReadoutBlockVariant.value, value: this.value, valueType: this.valueType, valueSize: this.valueSize, enhanced: this.rowEnhanced, weight: this.valueWeight, hintedZeros: this.valueOptions?.hintedZeros ?? false, hasSignSpacer: this.valueOptions?.hasSignSpacer ?? false, spaceReserver: this.valueOptions?.spaceReserver, off: this.off, hasIcon: this.valueOptions?.hasIcon ?? false }) : nothing} ${this.renderValueUnitGap()} <div class=\"unit-area\" part=\"unit-area\"> ${this.renderTrailingUnit()} ${this.renderDegreeSpacer()} </div> ${this.renderValueAlertOverlay()} </div> `; } /** * The value alert frame, drawn as a pure overlay around the value reading * (value + degree + unit). It reserves no space — the `obc-alert-frame` sits in * an absolutely-positioned box offset outward (see `.value-alert-overlay` in * the CSS) so its stroke is centred on the 4px/2px padding line and toggling it * never shifts content or row height. Renders only when `valueOptions.alert` is * a config object. */ renderValueAlertOverlay() { const alert = this.valueOptions?.alert; if (typeof alert !== \"object\" || alert === null) { return nothing; } const thickness = alert.thickness ?? ObcAlertFrameThickness.Small; return html` <div class=${classMap({ \"value-alert-overlay\": true, // The outward offset is thickness-dependent (see the CSS): large frames // draw a wider outline, so the box must sit further out to stay centred. \"thickness-large\": thickness === ObcAlertFrameThickness.Large })} aria-hidden=\"true\" > <obc-alert-frame part=\"value-alert-frame\" .type=${alert.type ?? ObcAlertFrameType.Regular} .thickness=${thickness} .status=${alert.status ?? AlertType.Alarm} .mode=${alert.mode ?? ObcAlertFrameMode.ackedActive} .flashingSpeed=${alert.flashingSpeed ?? FlashingSpeed.Default} .showIcon=${alert.showIcon ?? false} .showAlertCategoryIcon=${alert.showAlertCategoryIcon ?? true} .wrapContent=${false} .fullWidth=${false} ></obc-alert-frame> </div> `; } renderLabelContainer() { const stacking = this.resolvedStacking; const showLeadingUnit = stacking === \"leading-unit\" && Boolean(this.unit); const showLeadingSrc = this.hasLeadingSrc && Boolean(this.src); return html` <div class=\"label-container\" part=\"label-container\"> ${this.hasLeadingIcon ? html`<span class=\"leading-icon\" aria-hidden=\"true\" ><slot name=\"leading-icon\"></slot ></span>` : nothing} <div class=\"label-stack\" part=\"label-stack\"> ${this.label ? this.renderTextbox( \"label\", this.label, this.labelOptions?.spaceReserver ) : nothing} ${showLeadingUnit ? this.renderTextbox( \"unit\", this.unit ?? \"\", this.unitOptions?.spaceReserver ) : nothing} ${showLeadingSrc ? this.renderSourceBlock() : nothing} </div> </div> `; } renderTrailingUnit() { if (!this.hasTrailingUnitBox) { return nothing; } return this.renderTextbox( \"unit\", this.unit ?? \"\", this.unitOptions?.spaceReserver ); } /** * The source text plus its optional state chip and deviation line. The * plain (regular, no-deviation) case renders the bare textbox; a state or a * deviation wraps it in the `.source-block` stack. */ renderSourceBlock() { const state2 = this.srcOptions?.state ?? \"regular\"; const deviation = this.srcOptions?.deviation; const hasDeviation = deviation !== void 0 && Number.isFinite(deviation); const box = this.renderTextbox( \"source\", this.src ?? \"\", this.srcOptions?.spaceReserver, this.srcOptions ); if (state2 === \"regular\" && !hasDeviation) { return box; } return html` <div class=${classMap({ \"source-block\": true, [`source-state-${state2}`]: state2 !== \"regular\" /* regular */ })} part=\"source-block\" > ${box} ${hasDeviation ? html`<span class=\"source-deviation\"> <obi-delta aria-hidden=\"true\"></obi-delta> <obc-textbox class=\"source-deviation-value\" .size=${ObcTextboxSize.xs} .tabularNums=${true} alignment=\"left\" >${deviation}</obc-textbox > </span>` : nothing} </div> `; } renderTrailingSource() { if (this.hasLeadingSrc || !this.src) { return nothing; } return html` <div class=\"divider\" part=\"divider\" aria-hidden=\"true\"></div> ${this.renderSourceBlock()} `; } renderContent() { return html` <div class=\"content\" part=\"content\"> ${this.renderLabelContainer()} <div class=\"value-area\" part=\"value-area\"> ${this.renderValueCluster()} </div> ${this.renderTrailingSource()} </div> `; } willUpdate(changed) { super.willUpdate(changed); assertReadoutValueType(\"obc-readout-list-item\", this.value, this.valueType); assertReadoutFractionDigits(\"obc-readout-list-item\", this.fractionDigits); } updated(changed) { super.updated(changed); const firstUpdate = !this.hasCompletedFirstUpdate; this.hasCompletedFirstUpdate = true; if (!this.isPopUp || this.setpointTouching) { this.clearDeferredSetpointHide(); return; } const shouldHide = this.hasSetpoint && this.isAtSetpoint; if (firstUpdate) { this.deferredSetpointHidePhase = shouldHide ? ReadoutBlockHidePhase.hidden : ReadoutBlockHidePhase.none; return; } if (!shouldHide) { this.clearDeferredSetpointHide(); return; } if (this.deferredSetpointHidePhase !== ReadoutBlockHidePhase.none) { return; } this.deferredSetpointHidePhase = ReadoutBlockHidePhase.hiding; window.clearTimeout(this.deferredSetpointHideTimer); this.deferredSetpointHideTimer = window.setTimeout(() => { this.deferredSetpointHidePhase = ReadoutBlockHidePhase.hidden; this.deferredSetpointHideTimer = void 0; }, 100); } clearDeferredSetpointHide() { if (this.deferredSetpointHidePhase !== ReadoutBlockHidePhase.none) { this.deferredSetpointHidePhase = ReadoutBlockHidePhase.none; } window.clearTimeout(this.deferredSetpointHideTimer); this.deferredSetpointHideTimer = void 0; } disconnectedCallback() { window.clearTimeout(this.deferredSetpointHideTimer); this.deferredSetpointHideTimer = void 0; if (this.deferredSetpointHidePhase === ReadoutBlockHidePhase.hiding) { this.deferredSetpointHidePhase = ReadoutBlockHidePhase.hidden; } super.disconnectedCallback(); } render() { const clickable = this.resolvedClickable; const dataQuality = this.dataQuality; const classes = classMap({ root: true, [`size-${this.resolvedSize}`]: true, [`stacking-${this.resolvedStacking}`]: true, [`priority-${this.resolvedPriority}`]: true, \"data-low-integrity\": dataQuality === ReadoutListItemDataQuality.lowIntegrity, \"data-invalid\": dataQuality === ReadoutListItemDataQuality.invalid, \"flip-flop\": this.isFlipFlop, clickable: Boolean(clickable), [`border-${clickable ? clickable.border : \"squared\"}`]: Boolean(clickable) }); const surface = html`<div class=\"surface\" part=\"surface\"> ${this.renderContent()} </div>`; const root = clickable ? html`<button class=${classes} part=\"root\" type=\"button\"> ${surface} </button>` : html`<div class=${classes} part=\"root\">${surface}</div>`; const alert = this.alert === true ? {} : this.alert; return wrapWithAlertFrame(alert, root, true); } }"
|
|
10225
|
+
"default": "class extends LitElement { constructor() { super(...arguments); this.hasValue = true; this.value = null; this.valueType = ReadoutValueType.number; this.off = false; this.offText = \"OFF\"; this.hasSetpoint = false; this.hasAdvice = false; this.clickable = false; this.hasLeadingIcon = false; this.hasDegree = false; this.hasDegreeSpacer = false; this.fractionDigits = 0; this.maxDigits = 0; this.alert = false; this.showDebugOverlay = false; this.deferredSetpointHidePhase = ReadoutBlockHidePhase.none; this.hasCompletedFirstUpdate = false; } get resolvedSize() { return this.size ?? ReadoutListItemSize.small; } get resolvedStacking() { return this.stacking ?? \"trailing-unit\"; } /** Both leading-src stackings move the source into the label stack. */ get hasLeadingSrc() { const stacking = this.resolvedStacking; return stacking === \"leading-src\" || stacking === \"leading-src-inline\"; } get resolvedPriority() { return this.priority ?? \"regular\"; } /** * Whether a unit box follows the value: a unit, or a reserver alone — the * blank reserved column keeps a unit-less row aligned with its neighbours. */ get hasTrailingUnitBox() { return this.resolvedStacking !== \"leading-unit\" && (Boolean(this.unit) || Boolean(this.unitOptions?.spaceReserver)); } get resolvedFractionDigits() { return this.fractionDigits ?? 0; } get resolvedMaxDigits() { return resolveReadoutDigitCount(this.maxDigits); } /** * Whether a digit knob failed to arrive (`NaN` / `null` / `undefined`). * The raw knobs are forwarded to the blocks, which render the reading as * the unavailable dash (see `obc-readout-block.digitCountsMissing`); this * getter only keeps the setpoint comparison in agreement with what is * displayed. */ get digitCountsMissing() { return isReadoutDigitCountMissing(this.fractionDigits) || isReadoutDigitCountMissing(this.maxDigits); } get resolvedClickable() { const clickable = this.clickable; if (!clickable) { return false; } if (clickable === true) { return { border: \"squared\" /* squared */ }; } return { border: clickable.border ?? \"squared\" /* squared */ }; } get isAtSetpoint() { if (!this.hasSetpoint) { return false; } if (this.digitCountsMissing) { return false; } return isDisplayedAtSetpoint( resolveReadoutNumericValue(this.value, this.valueType) ?? null, this.setpoint, this.numericFormatOptions(this.resolvedMaxDigits) ); } get resolvedSetpointInteraction() { return this.setpointOptions?.interaction ?? \"always-visible\"; } get isFlipFlop() { return this.resolvedSetpointInteraction === \"flip-flop\"; } get isEqualSize() { return this.resolvedSetpointInteraction === \"equal-size\"; } get isPopUp() { return this.resolvedSetpointInteraction === \"pop-up\"; } get setpointTouching() { return this.setpointOptions?.touching ?? false; } /** * The setpoint is rendered \"emphasised\" (primary size + SemiBold weight) when * it is the focus of attention: while actively adjusting (`touching`), or while * a flip-flop has the value away from the setpoint. Otherwise it is a secondary * (smaller, regular-weight) reference next to the value. */ get isSetpointEmphasized() { if (!this.hasSetpoint) { return false; } if (this.setpointTouching) { return true; } return this.isFlipFlop && !this.isAtSetpoint; } /** * The row's enhanced (in-command) colour state, applied uniformly to BOTH the * value and the setpoint — they are always either both neutral or both enhanced * (never a blue setpoint next to a grey value). Driven by `priority` only; * `valueOptions.weight` changes weight, not colour. */ get rowEnhanced() { return this.resolvedPriority === \"enhanced\"; } /** Primary value-typography size for the current density tier. */ get primarySize() { return readoutPrimarySize(this.resolvedSize); } /** Secondary (de-emphasised) value-typography size for the current density tier. */ get secondarySize() { return readoutSecondarySize(this.resolvedSize); } get valueSize() { return readoutValueSize({ primary: this.primarySize, secondary: this.secondarySize, setpointEmphasized: this.isSetpointEmphasized, equalSize: this.isEqualSize }); } get setpointSize() { return readoutSetpointSize({ primary: this.primarySize, secondary: this.secondarySize, emphasized: this.isSetpointEmphasized, equalSize: this.isEqualSize }); } /** Value font weight passes straight to obc-textbox; regular when unset. Colour is separate. */ get valueWeight() { return this.valueOptions?.weight ?? ObcTextboxFontWeight.regular; } /** Label size: `xs` for dense list rows unless overridden (see {@link ReadoutLabelOptions}). */ get labelSize() { return this.labelOptions?.size ?? ObcTextboxSize.xs; } /** * Label weight: SemiBold only for enhanced (in-command) rows, regular * otherwise — the designer reduced the amount of semibold in the interface * (Figma 6.1 review). */ get labelWeight() { return this.rowEnhanced ? ObcTextboxFontWeight.semibold : ObcTextboxFontWeight.regular; } /** Setpoint is SemiBold only while emphasised, otherwise regular weight. */ get setpointWeight() { return readoutSetpointWeight(this.isSetpointEmphasized); } numericFormatOptions(maxDigits) { return readoutNumericFormatOptions(maxDigits, this.resolvedFractionDigits); } /** classMap fragment for a block / source carrying per-block data quality. */ dataQualityClasses(dataQuality) { return readoutDataQualityClasses(dataQuality); } /** * Forward the matching list-item icon slot into the block's single `icon` * slot. The variant's default marker lives in `obc-readout-block` and shows * when nothing is assigned here (an empty forwarded slot flattens to nothing). */ renderForwardedIcon(variant) { if (variant === ReadoutBlockVariant.setpoint) { return html`<slot name=\"setpoint-icon\" slot=\"icon\"></slot>`; } if (variant === ReadoutBlockVariant.advice) { return html`<slot name=\"advice-icon\" slot=\"icon\"></slot>`; } return html`<slot name=\"value-icon\" slot=\"icon\"></slot>`; } renderBlock(config) { return html` <obc-readout-block exportparts=\"block, block-content, block-text, block-icon, degree\" .variant=${config.variant} .value=${config.value ?? null} .valueType=${config.valueType ?? ReadoutValueType.number} .size=${this.resolvedSize} .valueSize=${config.valueSize} .enhanced=${config.enhanced} .weight=${config.weight} .hasDegree=${config.hasDegree ?? false} .hasIcon=${config.hasIcon ?? false} .fractionDigits=${this.fractionDigits} .maxDigits=${this.maxDigits} .hintedZeros=${config.hintedZeros} .hasSignSpacer=${config.hasSignSpacer ?? false} .spaceReserver=${config.spaceReserver} .off=${config.off ?? false} .offText=${this.offText} .touching=${config.touching ?? false} .hidePhase=${config.hidePhase ?? ReadoutBlockHidePhase.none} .dataQuality=${config.dataQuality} .alert=${config.alert ?? false} .category=${config.category ?? ReadoutAdviceCategory.regular} .active=${config.active ?? false} > ${this.renderForwardedIcon(config.variant)} </obc-readout-block> `; } /** * A cap-height `°` column whose width scales with the value size. Used after * the value (as the value↔unit boundary, via {@link renderValueUnitGap}) and * inside the setpoint / advice blocks. `inherit` makes the glyph take the * surrounding block's colour (setpoint/advice); otherwise it uses the value * colour, optionally `enhanced`. */ renderDegreeGlyph(size, opts = {}) { return html` <span class=${classMap({ \"degree-column\": true, [`degree-${size}`]: true, \"tone-enhanced\": !opts.inherit && Boolean(opts.enhanced), \"degree-inherit\": Boolean(opts.inherit) })} part=\"degree\" > <obc-textbox class=\"degree-glyph\" .size=${size} alignment=\"center\" >°</obc-textbox > </span> `; } /** * The gap rendered between the value digits and the unit. * * - `hasDegree`: a cap-height `°` column whose width scales with the value * size (the `°` replaces the default gap). * - otherwise: the default 2px gap (only when a trailing unit follows). * * `hasDegreeSpacer` deliberately does NOT add anything here — it keeps the 2px * gap and instead widens the unit column via {@link renderDegreeSpacer} (a * spacer AFTER the unit). That way a non-degree row's value digits stay * aligned with degree rows (degree column width = spacer width + 2px gap) * while its unit shifts left. Mirrors Figma `1:2920` (spacer) / `1:2970` * (degree). * * TODO(designer): cross-size alignment is deferred. Degree rows of different * value sizes have different `°` column widths (6/8/12px), so their value digit * edges stagger by `degree-width`. For degree rows of mixed sizes you cannot * align the value digit edges AND keep the unit column aligned — resolving it * needs a design decision (a constant degree reserve, which widens the smaller * rows' `°`, OR pinning the value edge and letting the unit column stagger). */ renderValueUnitGap() { if (!this.hasValue) { return nothing; } if ((this.hasDegree ?? false) && !this.off) { return this.renderDegreeGlyph(this.valueSize, { enhanced: this.rowEnhanced }); } if (this.hasTrailingUnitBox) { return html`<span class=\"value-unit-gap\" aria-hidden=\"true\"></span>`; } return nothing; } /** * A spacer rendered AFTER the unit when `hasDegreeSpacer` is set on a * non-degree row. Its width (`degree-compensation-padding`) = the degree * column width minus the 2px gap, so the row's value digits align with degree * rows in the same column while its unit shifts left. See * {@link renderValueUnitGap}. */ renderDegreeSpacer() { const hasDegree = this.hasDegree ?? false; const hasDegreeSpacer = this.hasDegreeSpacer ?? false; if (hasDegree || !hasDegreeSpacer) { return nothing; } return html`<span class=\"degree-spacer\" part=\"degree-spacer\" aria-hidden=\"true\" ></span>`; } renderTextbox(role, text, reserver, state2) { const weight = role === \"label\" ? this.labelWeight : ObcTextboxFontWeight.regular; const size = role === \"label\" ? this.labelSize : ObcTextboxSize.xs; const box = html` <obc-textbox class=${classMap({ [role]: true, ...this.dataQualityClasses(state2?.dataQuality) })} part=${role} .size=${size} .fontWeight=${weight} alignment=\"left\" > ${text} ${reserver ? html`<span slot=\"length\">${reserver}</span>` : nothing} </obc-textbox> `; return wrapWithAlertFrame(state2?.alert ?? false, box); } renderValueCluster() { const popUpAtSetpoint = this.isPopUp && this.isAtSetpoint && !this.setpointTouching; const setpointHidePhase = resolveSetpointHidePhase( popUpAtSetpoint, this.deferredSetpointHidePhase ); return html` <div class=\"value-cluster\" part=\"value-cluster\"> ${this.hasAdvice ? this.renderBlock({ variant: ReadoutBlockVariant.advice, value: this.advice, valueSize: this.secondarySize, enhanced: false, weight: ObcTextboxFontWeight.regular, hintedZeros: this.adviceOptions?.hintedZeros ?? false, hasSignSpacer: this.adviceOptions?.hasSignSpacer ?? false, spaceReserver: this.adviceOptions?.spaceReserver, hasDegree: this.hasDegree ?? false, dataQuality: this.adviceOptions?.dataQuality, alert: this.adviceOptions?.alert, category: this.adviceOptions?.category, active: this.adviceOptions?.active }) : nothing} ${this.hasSetpoint ? this.renderBlock({ variant: ReadoutBlockVariant.setpoint, value: this.setpoint, valueSize: this.setpointSize, // Value and setpoint share the enhanced colour state (both neutral // or both enhanced); the setpoint is bold only while emphasised. enhanced: this.rowEnhanced, weight: this.setpointWeight, hintedZeros: this.setpointOptions?.hintedZeros ?? false, hasSignSpacer: this.setpointOptions?.hasSignSpacer ?? false, spaceReserver: this.setpointOptions?.spaceReserver, hasDegree: this.hasDegree ?? false, touching: this.setpointTouching, hidePhase: setpointHidePhase, dataQuality: this.setpointOptions?.dataQuality, alert: this.setpointOptions?.alert }) : nothing} ${this.renderValueReading()} </div> `; } /** * The value reading: the value block, its degree column and the trailing unit * grouped in one relatively-positioned wrapper so the value alert frame AND the * value data-quality chip can wrap value + degree + unit together (Figma * 58:10120). * * Moving the degree / unit into this wrapper (the new last child of * `.value-cluster`) preserves every existing gap, so the underlying content * stays column-aligned with or without the frame / chip. Both the value alert * (overlay) and the value data-quality chip are applied here — NOT inside * `obc-readout-block` — so they extend over the unit; setpoint/advice keep their * own block-level frame and chip. The chip uses `outline` (not `border`), so it * never shifts the value's width / column alignment. */ renderValueReading() { return html` <div class=${classMap({ \"value-reading\": true, ...this.dataQualityClasses(this.valueOptions?.dataQuality) })} part=\"value-reading\" > ${this.hasValue ? this.renderBlock({ variant: ReadoutBlockVariant.value, value: this.value, valueType: this.valueType, valueSize: this.valueSize, enhanced: this.rowEnhanced, weight: this.valueWeight, hintedZeros: this.valueOptions?.hintedZeros ?? false, hasSignSpacer: this.valueOptions?.hasSignSpacer ?? false, spaceReserver: this.valueOptions?.spaceReserver, off: this.off, hasIcon: this.valueOptions?.hasIcon ?? false }) : nothing} ${this.renderValueUnitGap()} <div class=\"unit-area\" part=\"unit-area\"> ${this.renderTrailingUnit()} ${this.renderDegreeSpacer()} </div> ${this.renderValueAlertOverlay()} </div> `; } /** * The value alert frame, drawn as a pure overlay around the value reading * (value + degree + unit). It reserves no space — the `obc-alert-frame` sits in * an absolutely-positioned box offset outward (see `.value-alert-overlay` in * the CSS) so its stroke is centred on the 4px/2px padding line and toggling it * never shifts content or row height. Renders only when `valueOptions.alert` is * a config object. */ renderValueAlertOverlay() { const alert = this.valueOptions?.alert; if (typeof alert !== \"object\" || alert === null) { return nothing; } const thickness = alert.thickness ?? ObcAlertFrameThickness.Small; return html` <div class=\"value-alert-overlay\" aria-hidden=\"true\"> <obc-alert-frame part=\"value-alert-frame\" .type=${alert.type ?? ObcAlertFrameType.Regular} .thickness=${thickness} .status=${alert.status ?? AlertType.Alarm} .mode=${alert.mode ?? ObcAlertFrameMode.ackedActive} .flashingSpeed=${alert.flashingSpeed ?? FlashingSpeed.Default} .showIcon=${alert.showIcon ?? false} .showAlertCategoryIcon=${alert.showAlertCategoryIcon ?? true} .wrapContent=${false} .fullWidth=${false} ></obc-alert-frame> </div> `; } renderLabelContainer() { const stacking = this.resolvedStacking; const showLeadingUnit = stacking === \"leading-unit\" && Boolean(this.unit); const showLeadingSrc = this.hasLeadingSrc && Boolean(this.src); return html` <div class=\"label-container\" part=\"label-container\"> ${this.hasLeadingIcon ? html`<span class=\"leading-icon\" aria-hidden=\"true\" ><slot name=\"leading-icon\"></slot ></span>` : nothing} <div class=\"label-stack\" part=\"label-stack\"> ${this.label ? this.renderTextbox( \"label\", this.label, this.labelOptions?.spaceReserver ) : nothing} ${showLeadingUnit ? this.renderTextbox( \"unit\", this.unit ?? \"\", this.unitOptions?.spaceReserver ) : nothing} ${showLeadingSrc ? this.renderSourceBlock() : nothing} </div> </div> `; } renderTrailingUnit() { if (!this.hasTrailingUnitBox) { return nothing; } return this.renderTextbox( \"unit\", this.unit ?? \"\", this.unitOptions?.spaceReserver ); } /** * The source text plus its optional state chip and deviation line. The * plain (regular, no-deviation) case renders the bare textbox; a state or a * deviation wraps it in the `.source-block` stack. */ renderSourceBlock() { const state2 = this.srcOptions?.state ?? \"regular\"; const deviation = this.srcOptions?.deviation; const hasDeviation = deviation !== void 0 && Number.isFinite(deviation); const box = this.renderTextbox( \"source\", this.src ?? \"\", this.srcOptions?.spaceReserver, this.srcOptions ); if (state2 === \"regular\" && !hasDeviation) { return box; } return html` <div class=${classMap({ \"source-block\": true, [`source-state-${state2}`]: state2 !== \"regular\" /* regular */ })} part=\"source-block\" > ${box} ${hasDeviation ? html`<span class=\"source-deviation\"> <obi-delta aria-hidden=\"true\"></obi-delta> <obc-textbox class=\"source-deviation-value\" .size=${ObcTextboxSize.xs} .tabularNums=${true} alignment=\"left\" >${deviation}</obc-textbox > </span>` : nothing} </div> `; } renderTrailingSource() { if (this.hasLeadingSrc || !this.src) { return nothing; } return html` <div class=\"divider\" part=\"divider\" aria-hidden=\"true\"></div> ${this.renderSourceBlock()} `; } renderContent() { return html` <div class=\"content\" part=\"content\"> ${this.renderLabelContainer()} <div class=\"value-area\" part=\"value-area\"> ${this.renderValueCluster()} </div> ${this.renderTrailingSource()} </div> `; } willUpdate(changed) { super.willUpdate(changed); assertReadoutValueType(\"obc-readout-list-item\", this.value, this.valueType); assertReadoutFractionDigits(\"obc-readout-list-item\", this.fractionDigits); } updated(changed) { super.updated(changed); const firstUpdate = !this.hasCompletedFirstUpdate; this.hasCompletedFirstUpdate = true; if (!this.isPopUp || this.setpointTouching) { this.clearDeferredSetpointHide(); return; } const shouldHide = this.hasSetpoint && this.isAtSetpoint; if (firstUpdate) { this.deferredSetpointHidePhase = shouldHide ? ReadoutBlockHidePhase.hidden : ReadoutBlockHidePhase.none; return; } if (!shouldHide) { this.clearDeferredSetpointHide(); return; } if (this.deferredSetpointHidePhase !== ReadoutBlockHidePhase.none) { return; } this.deferredSetpointHidePhase = ReadoutBlockHidePhase.hiding; window.clearTimeout(this.deferredSetpointHideTimer); this.deferredSetpointHideTimer = window.setTimeout(() => { this.deferredSetpointHidePhase = ReadoutBlockHidePhase.hidden; this.deferredSetpointHideTimer = void 0; }, 100); } clearDeferredSetpointHide() { if (this.deferredSetpointHidePhase !== ReadoutBlockHidePhase.none) { this.deferredSetpointHidePhase = ReadoutBlockHidePhase.none; } window.clearTimeout(this.deferredSetpointHideTimer); this.deferredSetpointHideTimer = void 0; } disconnectedCallback() { window.clearTimeout(this.deferredSetpointHideTimer); this.deferredSetpointHideTimer = void 0; if (this.deferredSetpointHidePhase === ReadoutBlockHidePhase.hiding) { this.deferredSetpointHidePhase = ReadoutBlockHidePhase.hidden; } super.disconnectedCallback(); } render() { const clickable = this.resolvedClickable; const dataQuality = this.dataQuality; const classes = classMap({ root: true, [`size-${this.resolvedSize}`]: true, [`stacking-${this.resolvedStacking}`]: true, [`priority-${this.resolvedPriority}`]: true, \"data-low-integrity\": dataQuality === ReadoutListItemDataQuality.lowIntegrity, \"data-invalid\": dataQuality === ReadoutListItemDataQuality.invalid, \"flip-flop\": this.isFlipFlop, clickable: Boolean(clickable), [`border-${clickable ? clickable.border : \"squared\"}`]: Boolean(clickable) }); const surface = html`<div class=\"surface\" part=\"surface\"> ${this.renderContent()} </div>`; const root = clickable ? html`<button class=${classes} part=\"root\" type=\"button\"> ${surface} </button>` : html`<div class=${classes} part=\"root\">${surface}</div>`; const alert = this.alert === true ? {} : this.alert; return wrapWithAlertFrame(alert, root, true); } }"
|
|
10164
10226
|
}
|
|
10165
10227
|
],
|
|
10166
10228
|
"exports": [
|
|
@@ -10282,7 +10344,7 @@
|
|
|
10282
10344
|
{
|
|
10283
10345
|
"kind": "variable",
|
|
10284
10346
|
"name": "ObcReadout",
|
|
10285
|
-
"default": "class extends LitElement { constructor() { super(...arguments); this.hasValue = true; this.value = null; this.valueType = ReadoutValueType.number; this.off = false; this.offText = \"OFF\"; this.hasSetpoint = false; this.hasAdvice = false; this.hasDegree = false; this.hasDegreeSpacer = false; this.hasLeadingIcon = false; this.fractionDigits = 0; this.maxDigits = 0; this.alert = false; this.showDebugOverlay = false; this.deferredSetpointHidePhase = ReadoutBlockHidePhase.none; this.hasCompletedFirstUpdate = false; this.hasWarnedHorizontalSize = false; this.sourcePickerContentVisible = false; this.sourcePickerOptions = []; this.onWindowPointerDown = (event) => { if (!this.sourcePickerContentVisible) { return; } if (event.composedPath().includes(this)) { return; } this.sourcePickerContentVisible = false; }; this.syncSourcePickerOptions = () => { this.sourcePickerOptions = this.getSourcePickerNavigationItems().map( (item, index) => { const { itemLabel, itemValue } = this.getSourcePickerItemInfo( item, index ); return { value: itemValue, label: itemLabel, icon: this.createSourcePickerOptionIcon(item) }; } ); }; } get resolvedSize() { if (this.isHorizontal) { return ReadoutSize.large; } return this.size ?? ReadoutSize.small; } get resolvedPriority() { return this.priority ?? ReadoutPriority.regular; } get resolvedDirection() { return this.direction ?? \"vertical\"; } get resolvedStacking() { return this.stacking ?? \"inline\"; } get resolvedAlignment() { return this.alignment ?? \"vertical\"; } get isHorizontal() { return this.resolvedDirection === \"horizontal\"; } get resolvedFractionDigits() { return this.fractionDigits ?? 0; } get resolvedMaxDigits() { return resolveReadoutDigitCount(this.maxDigits); } /** * Whether a digit knob failed to arrive (`NaN` / `null` / `undefined`). * The raw knobs are forwarded to the blocks, which render the reading as * the unavailable dash (see `obc-readout-block.digitCountsMissing`); this * getter only keeps the setpoint comparison in agreement with what is * displayed. */ get digitCountsMissing() { return isReadoutDigitCountMissing(this.fractionDigits) || isReadoutDigitCountMissing(this.maxDigits); } get hasSrc() { return this.src !== void 0 && this.src.trim() !== \"\"; } get resolvedSourceInteraction() { return this.srcOptions?.interaction ?? \"none\"; } get isAtSetpoint() { if (!this.hasSetpoint) { return false; } if (this.digitCountsMissing) { return false; } return isDisplayedAtSetpoint( resolveReadoutNumericValue(this.value, this.valueType) ?? null, this.setpoint, this.numericFormatOptions(this.resolvedMaxDigits) ); } get resolvedSetpointInteraction() { return this.setpointOptions?.interaction ?? ReadoutSetpointInteraction.alwaysVisible; } get isFlipFlop() { return this.resolvedSetpointInteraction === ReadoutSetpointInteraction.flipFlop; } get isEqualSize() { return this.resolvedSetpointInteraction === ReadoutSetpointInteraction.equalSize; } get isPopUp() { return this.resolvedSetpointInteraction === ReadoutSetpointInteraction.popUp; } get setpointTouching() { return this.setpointOptions?.touching ?? false; } /** * Whether the setpoint interaction re-sizes or hides blocks at runtime — * flip-flop (emphasis swap) and pop-up (touch swap + fade-out) — the modes * that need the width/height reservation so the readout never shifts (see * {@link renderSetpointWidthReserve}). */ get hasDynamicSetpoint() { return this.hasSetpoint && (this.isFlipFlop || this.isPopUp); } /** * The setpoint is rendered \"emphasised\" (primary size + SemiBold weight) when * it is the focus of attention: while actively adjusting (`touching`), or while * a flip-flop has the value away from the setpoint. Otherwise it is a secondary * (smaller, regular-weight) reference next to the value. */ get isSetpointEmphasized() { if (!this.hasSetpoint) { return false; } if (this.setpointTouching) { return true; } return this.isFlipFlop && !this.isAtSetpoint; } /** * The readout's enhanced (in-command) colour state, applied uniformly to BOTH * the value and the setpoint — they are always either both neutral or both * enhanced (never a blue setpoint next to a grey value). Driven by `priority` * only; `valueOptions.weight` changes weight, not colour. */ get rowEnhanced() { return this.resolvedPriority === ReadoutPriority.enhanced; } /** Primary value-typography size for the current density tier. */ get primarySize() { return readoutPrimarySize(this.resolvedSize); } /** Secondary (de-emphasised) value-typography size for the current density tier. */ get secondarySize() { return readoutSecondarySize(this.resolvedSize); } get valueSize() { return readoutValueSize({ primary: this.primarySize, secondary: this.secondarySize, setpointEmphasized: this.isSetpointEmphasized, equalSize: this.isEqualSize }); } get setpointSize() { return readoutSetpointSize({ primary: this.primarySize, secondary: this.secondarySize, emphasized: this.isSetpointEmphasized, equalSize: this.isEqualSize }); } /** Value font weight passes straight to obc-textbox; regular when unset. Colour is separate. */ get valueWeight() { return this.valueOptions?.weight ?? ObcTextboxFontWeight.regular; } /** * Label size. The Figma 6.1 title block defaults large readouts to `s` — a * scannable label should not sit at the smallest permitted size — while * small/medium tiers stay `xs`; `labelOptions.size` overrides either way * (per the design team, for context / density / secondary instruments). * TODO(designer): the medium-tier default is unverified in the new sheets * (only XS and S exist on the title's size axis). */ get labelSize() { return this.labelOptions?.size ?? (this.resolvedSize === ReadoutSize.large ? ObcTextboxSize.s : ObcTextboxSize.xs); } /** * Label weight: SemiBold only for enhanced (in-command) readouts, regular * otherwise — the designer reduced the amount of semibold in the interface * (Figma 6.1 review). */ get labelWeight() { return this.rowEnhanced ? ObcTextboxFontWeight.semibold : ObcTextboxFontWeight.regular; } /** Setpoint is SemiBold only while emphasised, otherwise regular weight. */ get setpointWeight() { return readoutSetpointWeight(this.isSetpointEmphasized); } numericFormatOptions(maxDigits) { return readoutNumericFormatOptions(maxDigits, this.resolvedFractionDigits); } /** classMap fragment for a block / source carrying per-block data quality. */ dataQualityClasses(dataQuality) { return readoutDataQualityClasses(dataQuality); } /** * Forward the matching readout icon slot into the block's single `icon` * slot. The variant's default marker lives in `obc-readout-block` and shows * when nothing is assigned here (an empty forwarded slot flattens to nothing). */ renderForwardedIcon(variant) { if (variant === ReadoutBlockVariant.setpoint) { return html`<slot name=\"setpoint-icon\" slot=\"icon\"></slot>`; } if (variant === ReadoutBlockVariant.advice) { return html`<slot name=\"advice-icon\" slot=\"icon\"></slot>`; } return html`<slot name=\"value-icon\" slot=\"icon\"></slot>`; } renderBlock(config) { return html` <obc-readout-block exportparts=\"block, block-content, block-text, block-icon, degree\" .variant=${config.variant} .value=${config.value ?? null} .valueType=${config.valueType ?? ReadoutValueType.number} .size=${this.resolvedSize} .valueSize=${config.valueSize} .enhanced=${config.enhanced} .weight=${config.weight} .hasDegree=${config.hasDegree ?? false} .hasIcon=${config.hasIcon ?? false} .fractionDigits=${this.fractionDigits} .maxDigits=${this.maxDigits} .hintedZeros=${config.hintedZeros} .hasSignSpacer=${config.hasSignSpacer ?? false} .spaceReserver=${config.spaceReserver} .off=${config.off ?? false} .offText=${this.offText} .touching=${config.touching ?? false} .hidePhase=${config.hidePhase ?? ReadoutBlockHidePhase.none} .dataQuality=${config.dataQuality} .alert=${config.alert ?? false} .category=${config.category ?? ReadoutAdviceCategory.regular} .active=${config.active ?? false} > ${config.ghost ? nothing : this.renderForwardedIcon(config.variant)} </obc-readout-block> `; } /** * A cap-height `°` column whose width scales with the value size, rendered * after the value block. Setpoint / advice blocks render their own degree via * `obc-readout-block`. */ renderDegreeGlyph(size) { return html` <span class=${classMap({ \"degree-column\": true, [`degree-${size}`]: true, \"tone-enhanced\": this.rowEnhanced })} part=\"degree\" > <obc-textbox class=\"degree-glyph\" .size=${size} alignment=\"center\" >°</obc-textbox > </span> `; } /** * The column rendered after the value digits: * * - `hasDegree`: a cap-height `°` column whose width scales with the value size. * - `hasDegreeSpacer` (non-degree readouts): an invisible reserve of the same * width, so this readout's value digits stay aligned with degree readouts * in the same column. */ renderDegreeColumn(size = this.valueSize) { if ((this.hasDegree ?? false) && !this.off) { return this.renderDegreeGlyph(size); } if (this.hasDegreeSpacer ?? false) { return html`<span class=\"degree-spacer\" part=\"degree-spacer\" aria-hidden=\"true\" ></span>`; } return nothing; } renderTextbox(role, text, reserver, blockState) { const weight = role === \"label\" ? this.labelWeight : ObcTextboxFontWeight.regular; const size = role === \"label\" ? this.labelSize : ObcTextboxSize.xs; const box = html` <obc-textbox class=${classMap({ [role]: true, ...this.dataQualityClasses(blockState?.dataQuality) })} part=${role} .size=${size} .fontWeight=${weight} alignment=\"left\" > ${text} ${reserver ? html`<span slot=\"length\">${reserver}</span>` : nothing} </obc-textbox> `; return wrapWithAlertFrame(blockState?.alert ?? false, box); } get setpointHidePhase() { const popUpAtSetpoint = this.isPopUp && this.isAtSetpoint && !this.setpointTouching; return resolveSetpointHidePhase( popUpAtSetpoint, this.deferredSetpointHidePhase ); } // `hasDegree` is deliberately NOT forwarded to the advice / setpoint blocks: // the unit is written once per readout and the degree follows the same rule // (Figma 6.1). `obc-readout-list-item` keeps its own per-row convention. renderAdviceBlock() { return this.renderBlock({ variant: ReadoutBlockVariant.advice, value: this.advice, valueSize: this.secondarySize, enhanced: false, weight: ObcTextboxFontWeight.regular, hintedZeros: this.adviceOptions?.hintedZeros ?? false, hasSignSpacer: this.adviceOptions?.hasSignSpacer ?? false, spaceReserver: this.adviceOptions?.spaceReserver, dataQuality: this.adviceOptions?.dataQuality, alert: this.adviceOptions?.alert, category: this.adviceOptions?.category, active: this.adviceOptions?.active }); } renderSetpointBlock() { return this.renderBlock({ variant: ReadoutBlockVariant.setpoint, value: this.setpoint, valueSize: this.setpointSize, // Value and setpoint share the enhanced colour state (both neutral // or both enhanced); the setpoint is bold only while emphasised. enhanced: this.rowEnhanced, weight: this.setpointWeight, hintedZeros: this.setpointOptions?.hintedZeros ?? false, hasSignSpacer: this.setpointOptions?.hasSignSpacer ?? false, spaceReserver: this.setpointOptions?.spaceReserver, touching: this.setpointTouching, hidePhase: this.setpointHidePhase, dataQuality: this.setpointOptions?.dataQuality, alert: this.setpointOptions?.alert }); } /** * The value reading: the value block and its degree column (or spacer) * grouped in one relatively-positioned wrapper so the value alert frame AND * the value data-quality chip can wrap both together. Unlike * `obc-readout-list-item` the unit is not part of the reading — it lives in * the label/unit meta zone. */ renderValueReading() { return html` <div class=${classMap({ \"value-reading\": true, ...this.dataQualityClasses(this.valueOptions?.dataQuality) })} part=\"value-reading\" > ${this.renderBlock({ variant: ReadoutBlockVariant.value, value: this.value, valueType: this.valueType, valueSize: this.valueSize, enhanced: this.rowEnhanced, weight: this.valueWeight, hintedZeros: this.valueOptions?.hintedZeros ?? false, hasSignSpacer: this.valueOptions?.hasSignSpacer ?? false, spaceReserver: this.valueOptions?.spaceReserver, off: this.off, hasIcon: this.valueOptions?.hasIcon ?? false })} ${this.renderDegreeColumn()} ${this.renderValueAlertOverlay()} </div> `; } /** * The value alert frame, drawn as a pure overlay around the value reading * (value + degree). It reserves no space — the `obc-alert-frame` sits in an * absolutely-positioned box offset outward (see `.value-alert-overlay` in the * CSS) so its stroke is centred on the 4px/2px padding line and toggling it * never shifts content. Renders only when `valueOptions.alert` is a config * object. */ renderValueAlertOverlay() { const alert = this.valueOptions?.alert; if (typeof alert !== \"object\" || alert === null) { return nothing; } const thickness = alert.thickness ?? ObcAlertFrameThickness.Small; return html` <div class=${classMap({ \"value-alert-overlay\": true, // The outward offset is thickness-dependent (see the CSS): large frames // draw a wider outline, so the box must sit further out to stay centred. \"thickness-large\": thickness === ObcAlertFrameThickness.Large })} aria-hidden=\"true\" > <obc-alert-frame part=\"value-alert-frame\" .type=${alert.type ?? ObcAlertFrameType.Regular} .thickness=${thickness} .status=${alert.status ?? AlertType.Alarm} .mode=${alert.mode ?? ObcAlertFrameMode.ackedActive} .flashingSpeed=${alert.flashingSpeed ?? FlashingSpeed.Default} .showIcon=${alert.showIcon ?? false} .showAlertCategoryIcon=${alert.showAlertCategoryIcon ?? true} .wrapContent=${false} .fullWidth=${false} ></obc-alert-frame> </div> `; } /** The label/unit meta zone; `stacking` picks the inline or stacked arrangement. */ renderMetaZone() { if (!this.label && !this.unit) { return nothing; } const stacked = this.isHorizontal || this.resolvedStacking === \"stacked\"; return html` <div class=${classMap({ meta: true, \"meta-inline\": !stacked, \"meta-stacked\": stacked, \"label-only\": !!this.label && !this.unit, \"unit-only\": !!this.unit && !this.label })} part=\"meta-wrapper\" > ${this.hasLeadingIcon ? html`<span class=\"leading-icon\" aria-hidden=\"true\" ><slot name=\"leading-icon\"></slot ></span>` : nothing} <div class=\"meta-labels\" part=\"meta-labels\"> ${this.label ? this.renderTextbox( \"label\", this.label, this.labelOptions?.spaceReserver ) : nothing} ${this.unit ? this.renderTextbox( \"unit\", this.unit, this.unitOptions?.spaceReserver ) : nothing} </div> </div> `; } renderSourceContent() { const interaction = this.resolvedSourceInteraction; const src = this.src ?? \"\"; const buttonClasses = classMap({ \"source-button\": true, ...this.dataQualityClasses(this.srcOptions?.dataQuality) }); if (interaction === \"picker\") { const button = html` <obc-button class=${buttonClasses} variant=\"flat\" .fullWidth=${false} showTrailingIcon @click=${() => { this.sourcePickerContentVisible = !this.sourcePickerContentVisible; }} > <obi-drop-down-google slot=\"trailing-icon\"></obi-drop-down-google> ${src} </obc-button> `; return wrapWithAlertFrame(this.srcOptions?.alert ?? false, button); } if (interaction === \"flyout\") { const button = html` <obc-button class=${buttonClasses} variant=\"flat\" .fullWidth=${false} showTrailingIcon @click=${() => { this.dispatchEvent( new CustomEvent(\"source-flyout-click\", { bubbles: true, composed: true, detail: { src } }) ); }} > <obi-chevron-right-google slot=\"trailing-icon\" ></obi-chevron-right-google> ${src} </obc-button> `; return wrapWithAlertFrame(this.srcOptions?.alert ?? false, button); } return this.renderSourceBlock(src); } /** * The plain (non-interactive) source: the textbox plus its optional state * chip and deviation line. The regular no-deviation case renders the bare * textbox; a state or deviation wraps it in the `.source-block` stack. The * picker / flyout button variants do not carry state / deviation yet — * TODO(designer): undefined in the 6.1 sheets. */ renderSourceBlock(src) { const state2 = this.srcOptions?.state ?? ReadoutSourceState.regular; const deviation = this.srcOptions?.deviation; const hasDeviation = deviation !== void 0 && Number.isFinite(deviation); const box = this.renderTextbox( \"source\", src, this.srcOptions?.spaceReserver, this.srcOptions ); if (state2 === ReadoutSourceState.regular && !hasDeviation) { return box; } return html` <div class=${classMap({ \"source-block\": true, [`source-state-${state2}`]: state2 !== ReadoutSourceState.regular })} part=\"source-block\" > ${box} ${hasDeviation ? html`<span class=\"source-deviation\"> <obi-delta aria-hidden=\"true\"></obi-delta> <obc-textbox class=\"source-deviation-value\" .size=${ObcTextboxSize.xs} .tabularNums=${true} alignment=\"left\" >${deviation}</obc-textbox > </span>` : nothing} </div> `; } /** * The source row (vertical) / segment (horizontal). Only the horizontal * direction draws an auto divider before the source — the Figma 6.1 review * (node 95:24490 \"remove dividers\", 95:26891 \"Source-divider are there\") * keeps the vertical source row divider-free while the horizontal flow * separates the source with a vertical rule. */ renderSource() { if (!this.hasSrc) { return nothing; } return html` ${this.isHorizontal ? html`<div class=\"divider divider-vertical\" part=\"divider\" aria-hidden=\"true\" ></div>` : nothing} <div class=\"source-row\" part=\"source-wrapper\"> ${this.renderSourceContent()} </div> `; } // ---------- Source picker (context menu built from src-picker-content) ---------- getSourcePickerNavigationItems() { const assignedElements = this.sourcePickerSlot?.assignedElements({ flatten: true }) ?? []; return assignedElements.flatMap((element) => { if (!(element instanceof HTMLElement)) { return []; } if (element.localName === \"obc-navigation-item\") { return [element]; } return Array.from( element.querySelectorAll(\"obc-navigation-item\") ); }); } createSourcePickerOptionIcon(element) { const iconElement = element.querySelector('[slot=\"icon\"]'); if (!(iconElement instanceof HTMLElement)) { return void 0; } return html`${iconElement.cloneNode(true)}`; } getSourcePickerItemInfo(item, index) { const itemWithValues = item; const itemLabel = itemWithValues.label || item.getAttribute(\"label\") || \"\"; const itemValue = item.getAttribute(\"data-value\") || itemWithValues.value || item.getAttribute(\"value\") || itemLabel || `source-option-${index}`; return { itemLabel, itemValue }; } findSourcePickerOptionElement(value) { const navigationItems = this.getSourcePickerNavigationItems(); for (const [index, item] of navigationItems.entries()) { const { itemValue } = this.getSourcePickerItemInfo(item, index); if (itemValue === value) { return item; } } return void 0; } handleSourcePickerItemClick(event) { this.dispatchEvent( new CustomEvent(\"source-change\", { bubbles: true, composed: true, detail: { value: event.detail.value, label: event.detail.option?.label } }) ); this.findSourcePickerOptionElement(event.detail.value)?.click(); this.sourcePickerContentVisible = false; } /** * The context menu highlights options by their VALUE, while `src` holds the * displayed source text — which may be an option's label when the two * differ (e.g. `data-value=\"gps-1\"` with label \"GPS 1\"). Resolve the current * source to the matching option's value (by value or label) so the open * picker always highlights the active source. */ get selectedSourceValues() { if (!this.src) { return []; } const match = this.sourcePickerOptions.find( (option) => option.value === this.src || option.label === this.src ); return [match?.value ?? this.src]; } renderSourcePickerContent() { if (this.resolvedSourceInteraction !== \"picker\" || !this.sourcePickerContentVisible) { return nothing; } return html` <obc-context-menu-input .type=${ContextMenuType.Regular} .options=${this.sourcePickerOptions} .selectedValues=${this.selectedSourceValues} class=\"source-picker-content\" @item-click=${this.handleSourcePickerItemClick} @close=${() => { this.sourcePickerContentVisible = false; }} ></obc-context-menu-input> `; } renderSourcePickerSlot() { return html` <slot name=\"src-picker-content\" hidden @slotchange=${this.syncSourcePickerOptions} ></slot> `; } // ---------- Layouts ---------- /** * An invisible primary-size duplicate of a dynamic setpoint row's content, * stacked under the visible block in the same grid cell purely to reserve * width. With both rows permanently holding their emphasised width, the * widest row — and therefore the readout's total width — stays constant * while a flip-flop swaps the value/setpoint sizes, while a touch swap * runs, and while a pop-up setpoint fades out, so the meta zone and source * never shift. This is the vertical analogue of the horizontal arrangement, * where the swapped pair sits on one row and its summed width is constant * by construction (the designer's flip-flop invariant). Only the vertical * direction needs it; `always-visible` layouts are untouched. */ renderSetpointWidthReserve(kind) { if (!this.hasDynamicSetpoint || this.isHorizontal) { return nothing; } const content = kind === \"setpoint\" ? this.renderBlock({ variant: ReadoutBlockVariant.setpoint, value: this.setpoint, valueSize: this.primarySize, enhanced: false, // The emphasised setpoint is SemiBold — reserve its widest form. // No degree: the visible setpoint block carries none (the degree // renders on the actual value only), so the ghost mirrors it. weight: ObcTextboxFontWeight.semibold, hintedZeros: this.setpointOptions?.hintedZeros ?? false, hasSignSpacer: this.setpointOptions?.hasSignSpacer ?? false, spaceReserver: this.setpointOptions?.spaceReserver, alert: this.setpointOptions?.alert, ghost: true }) : html`${this.renderBlock({ variant: ReadoutBlockVariant.value, value: this.value, valueType: this.valueType, valueSize: this.primarySize, enhanced: false, weight: this.valueWeight, hintedZeros: this.valueOptions?.hintedZeros ?? false, hasSignSpacer: this.valueOptions?.hasSignSpacer ?? false, spaceReserver: this.valueOptions?.spaceReserver, off: this.off, hasIcon: this.valueOptions?.hasIcon ?? false, ghost: true })}${this.renderDegreeColumn(this.primarySize)}`; return html`<div class=\"setpoint-width-reserve\" aria-hidden=\"true\"> ${content} </div>`; } /** * Vertical: advice / setpoint / value rows right-aligned in a cluster, the * label+unit meta zone below, then the source row under an auto divider. * `alignment` re-aligns the whole column for stacked readouts. */ renderVerticalLayout() { return html` <div class=\"value-cluster\" part=\"value-cluster\"> ${this.hasAdvice ? html`<div class=\"advice-row\" part=\"advice-wrapper\"> ${this.renderAdviceBlock()} </div>` : nothing} ${this.hasSetpoint ? html`<div class=\"setpoint-row\" part=\"setpoint-wrapper\"> ${this.renderSetpointBlock()} ${this.renderSetpointWidthReserve(\"setpoint\")} </div>` : nothing} ${this.hasValue ? html`<div class=\"value-row\" part=\"value-wrapper\"> ${this.renderValueReading()} ${this.renderSetpointWidthReserve(\"value\")} </div>` : nothing} </div> ${this.renderMetaZone()} ${this.renderSource()} `; } /** * Horizontal: advice, setpoint, value and the stacked label/unit meta flow on * one cap-height-aligned row, with the source at the end after an auto * divider. */ renderHorizontalLayout() { return html` <div class=\"inline-row\" part=\"value-cluster\"> ${this.hasAdvice ? html`<div class=\"advice-row\" part=\"advice-wrapper\"> ${this.renderAdviceBlock()} </div>` : nothing} ${this.hasSetpoint ? html`<div class=\"setpoint-row\" part=\"setpoint-wrapper\"> ${this.renderSetpointBlock()} </div>` : nothing} ${this.hasValue ? html`<div class=\"value-row\" part=\"value-wrapper\"> ${this.renderValueReading()} </div>` : nothing} ${this.renderMetaZone()} ${this.renderSource()} </div> `; } willUpdate(changed) { super.willUpdate(changed); assertReadoutValueType(\"obc-readout\", this.value, this.valueType); assertReadoutFractionDigits(\"obc-readout\", this.fractionDigits); this.warnHorizontalSizeIgnored(); } /** * A horizontal readout discards any `size` but `large` (see `resolvedSize`). * A silent discard is indistinguishable from a bug to a consumer, so each * offending element says so once and logs itself for locating (#1182). * A removed `size` attribute arrives as `null` and counts as unset. */ warnHorizontalSizeIgnored() { if (this.hasWarnedHorizontalSize || !this.isHorizontal || !this.size || this.size === ReadoutSize.large) { return; } this.hasWarnedHorizontalSize = true; console.warn( `[obc-readout] size=\"${this.size}\" is ignored when direction=\"horizontal\": the horizontal arrangement exists in the large tier only. Remove the size, or use direction=\"vertical\" for the small / medium tiers.`, this ); } updated(changed) { super.updated(changed); if (changed.has(\"sourcePickerContentVisible\")) { if (this.sourcePickerContentVisible) { window.addEventListener(\"pointerdown\", this.onWindowPointerDown, true); } else { window.removeEventListener( \"pointerdown\", this.onWindowPointerDown, true ); } } const firstUpdate = !this.hasCompletedFirstUpdate; this.hasCompletedFirstUpdate = true; if (!this.isPopUp || this.setpointTouching) { this.clearDeferredSetpointHide(); return; } const shouldHide = this.hasSetpoint && this.isAtSetpoint; if (firstUpdate) { this.deferredSetpointHidePhase = shouldHide ? ReadoutBlockHidePhase.hidden : ReadoutBlockHidePhase.none; return; } if (!shouldHide) { this.clearDeferredSetpointHide(); return; } if (this.deferredSetpointHidePhase !== ReadoutBlockHidePhase.none) { return; } this.deferredSetpointHidePhase = ReadoutBlockHidePhase.hiding; window.clearTimeout(this.deferredSetpointHideTimer); this.deferredSetpointHideTimer = window.setTimeout(() => { this.deferredSetpointHidePhase = ReadoutBlockHidePhase.hidden; this.deferredSetpointHideTimer = void 0; }, 100); } clearDeferredSetpointHide() { if (this.deferredSetpointHidePhase !== ReadoutBlockHidePhase.none) { this.deferredSetpointHidePhase = ReadoutBlockHidePhase.none; } window.clearTimeout(this.deferredSetpointHideTimer); this.deferredSetpointHideTimer = void 0; } disconnectedCallback() { window.removeEventListener(\"pointerdown\", this.onWindowPointerDown, true); window.clearTimeout(this.deferredSetpointHideTimer); this.deferredSetpointHideTimer = void 0; if (this.deferredSetpointHidePhase === ReadoutBlockHidePhase.hiding) { this.deferredSetpointHidePhase = ReadoutBlockHidePhase.hidden; } super.disconnectedCallback(); } render() { const dataQuality = this.dataQuality; const classes = classMap({ readout: true, [`size-${this.resolvedSize}`]: true, [`direction-${this.resolvedDirection}`]: true, [`stacking-${this.resolvedStacking}`]: true, [`alignment-${this.resolvedAlignment}`]: true, [`priority-${this.resolvedPriority}`]: true, \"data-low-integrity\": dataQuality === ReadoutDataQuality.lowIntegrity, \"data-invalid\": dataQuality === ReadoutDataQuality.invalid, \"flip-flop\": this.isFlipFlop, \"setpoint-dynamic\": this.hasDynamicSetpoint }); const root = html` <div class=${classes} part=\"root\"> ${this.isHorizontal ? this.renderHorizontalLayout() : this.renderVerticalLayout()} ${this.renderSourcePickerSlot()} </div> ${this.renderSourcePickerContent()} `; const alert = this.alert === true ? {} : this.alert; return wrapWithAlertFrame(alert, root); } }"
|
|
10347
|
+
"default": "class extends LitElement { constructor() { super(...arguments); this.hasValue = true; this.value = null; this.valueType = ReadoutValueType.number; this.off = false; this.offText = \"OFF\"; this.hasSetpoint = false; this.hasAdvice = false; this.hasDegree = false; this.hasDegreeSpacer = false; this.hasLeadingIcon = false; this.fractionDigits = 0; this.maxDigits = 0; this.alert = false; this.showDebugOverlay = false; this.deferredSetpointHidePhase = ReadoutBlockHidePhase.none; this.hasCompletedFirstUpdate = false; this.hasWarnedHorizontalSize = false; this.sourcePickerContentVisible = false; this.sourcePickerOptions = []; this.onWindowPointerDown = (event) => { if (!this.sourcePickerContentVisible) { return; } if (event.composedPath().includes(this)) { return; } this.sourcePickerContentVisible = false; }; this.syncSourcePickerOptions = () => { this.sourcePickerOptions = this.getSourcePickerNavigationItems().map( (item, index) => { const { itemLabel, itemValue } = this.getSourcePickerItemInfo( item, index ); return { value: itemValue, label: itemLabel, icon: this.createSourcePickerOptionIcon(item) }; } ); }; } get resolvedSize() { if (this.isHorizontal) { return ReadoutSize.large; } return this.size ?? ReadoutSize.small; } get resolvedPriority() { return this.priority ?? ReadoutPriority.regular; } get resolvedDirection() { return this.direction ?? \"vertical\"; } get resolvedStacking() { return this.stacking ?? \"inline\"; } get resolvedAlignment() { return this.alignment ?? \"vertical\"; } get isHorizontal() { return this.resolvedDirection === \"horizontal\"; } get resolvedFractionDigits() { return this.fractionDigits ?? 0; } get resolvedMaxDigits() { return resolveReadoutDigitCount(this.maxDigits); } /** * Whether a digit knob failed to arrive (`NaN` / `null` / `undefined`). * The raw knobs are forwarded to the blocks, which render the reading as * the unavailable dash (see `obc-readout-block.digitCountsMissing`); this * getter only keeps the setpoint comparison in agreement with what is * displayed. */ get digitCountsMissing() { return isReadoutDigitCountMissing(this.fractionDigits) || isReadoutDigitCountMissing(this.maxDigits); } get hasSrc() { return this.src !== void 0 && this.src.trim() !== \"\"; } get resolvedSourceInteraction() { return this.srcOptions?.interaction ?? \"none\"; } get isAtSetpoint() { if (!this.hasSetpoint) { return false; } if (this.digitCountsMissing) { return false; } return isDisplayedAtSetpoint( resolveReadoutNumericValue(this.value, this.valueType) ?? null, this.setpoint, this.numericFormatOptions(this.resolvedMaxDigits) ); } get resolvedSetpointInteraction() { return this.setpointOptions?.interaction ?? ReadoutSetpointInteraction.alwaysVisible; } get isFlipFlop() { return this.resolvedSetpointInteraction === ReadoutSetpointInteraction.flipFlop; } get isEqualSize() { return this.resolvedSetpointInteraction === ReadoutSetpointInteraction.equalSize; } get isPopUp() { return this.resolvedSetpointInteraction === ReadoutSetpointInteraction.popUp; } get setpointTouching() { return this.setpointOptions?.touching ?? false; } /** * Whether the setpoint interaction re-sizes or hides blocks at runtime — * flip-flop (emphasis swap) and pop-up (touch swap + fade-out) — the modes * that need the width/height reservation so the readout never shifts (see * {@link renderSetpointWidthReserve}). */ get hasDynamicSetpoint() { return this.hasSetpoint && (this.isFlipFlop || this.isPopUp); } /** * The setpoint is rendered \"emphasised\" (primary size + SemiBold weight) when * it is the focus of attention: while actively adjusting (`touching`), or while * a flip-flop has the value away from the setpoint. Otherwise it is a secondary * (smaller, regular-weight) reference next to the value. */ get isSetpointEmphasized() { if (!this.hasSetpoint) { return false; } if (this.setpointTouching) { return true; } return this.isFlipFlop && !this.isAtSetpoint; } /** * The readout's enhanced (in-command) colour state, applied uniformly to BOTH * the value and the setpoint — they are always either both neutral or both * enhanced (never a blue setpoint next to a grey value). Driven by `priority` * only; `valueOptions.weight` changes weight, not colour. */ get rowEnhanced() { return this.resolvedPriority === ReadoutPriority.enhanced; } /** Primary value-typography size for the current density tier. */ get primarySize() { return readoutPrimarySize(this.resolvedSize); } /** Secondary (de-emphasised) value-typography size for the current density tier. */ get secondarySize() { return readoutSecondarySize(this.resolvedSize); } get valueSize() { return readoutValueSize({ primary: this.primarySize, secondary: this.secondarySize, setpointEmphasized: this.isSetpointEmphasized, equalSize: this.isEqualSize }); } get setpointSize() { return readoutSetpointSize({ primary: this.primarySize, secondary: this.secondarySize, emphasized: this.isSetpointEmphasized, equalSize: this.isEqualSize }); } /** Value font weight passes straight to obc-textbox; regular when unset. Colour is separate. */ get valueWeight() { return this.valueOptions?.weight ?? ObcTextboxFontWeight.regular; } /** * Label size. The Figma 6.1 title block defaults large readouts to `s` — a * scannable label should not sit at the smallest permitted size — while * small/medium tiers stay `xs`; `labelOptions.size` overrides either way * (per the design team, for context / density / secondary instruments). * TODO(designer): the medium-tier default is unverified in the new sheets * (only XS and S exist on the title's size axis). */ get labelSize() { return this.labelOptions?.size ?? (this.resolvedSize === ReadoutSize.large ? ObcTextboxSize.s : ObcTextboxSize.xs); } /** * Label weight: SemiBold only for enhanced (in-command) readouts, regular * otherwise — the designer reduced the amount of semibold in the interface * (Figma 6.1 review). */ get labelWeight() { return this.rowEnhanced ? ObcTextboxFontWeight.semibold : ObcTextboxFontWeight.regular; } /** Setpoint is SemiBold only while emphasised, otherwise regular weight. */ get setpointWeight() { return readoutSetpointWeight(this.isSetpointEmphasized); } numericFormatOptions(maxDigits) { return readoutNumericFormatOptions(maxDigits, this.resolvedFractionDigits); } /** classMap fragment for a block / source carrying per-block data quality. */ dataQualityClasses(dataQuality) { return readoutDataQualityClasses(dataQuality); } /** * Forward the matching readout icon slot into the block's single `icon` * slot. The variant's default marker lives in `obc-readout-block` and shows * when nothing is assigned here (an empty forwarded slot flattens to nothing). */ renderForwardedIcon(variant) { if (variant === ReadoutBlockVariant.setpoint) { return html`<slot name=\"setpoint-icon\" slot=\"icon\"></slot>`; } if (variant === ReadoutBlockVariant.advice) { return html`<slot name=\"advice-icon\" slot=\"icon\"></slot>`; } return html`<slot name=\"value-icon\" slot=\"icon\"></slot>`; } renderBlock(config) { return html` <obc-readout-block exportparts=\"block, block-content, block-text, block-icon, degree\" .variant=${config.variant} .value=${config.value ?? null} .valueType=${config.valueType ?? ReadoutValueType.number} .size=${this.resolvedSize} .valueSize=${config.valueSize} .enhanced=${config.enhanced} .weight=${config.weight} .hasDegree=${config.hasDegree ?? false} .hasIcon=${config.hasIcon ?? false} .fractionDigits=${this.fractionDigits} .maxDigits=${this.maxDigits} .hintedZeros=${config.hintedZeros} .hasSignSpacer=${config.hasSignSpacer ?? false} .spaceReserver=${config.spaceReserver} .off=${config.off ?? false} .offText=${this.offText} .touching=${config.touching ?? false} .hidePhase=${config.hidePhase ?? ReadoutBlockHidePhase.none} .dataQuality=${config.dataQuality} .alert=${config.alert ?? false} .category=${config.category ?? ReadoutAdviceCategory.regular} .active=${config.active ?? false} > ${config.ghost ? nothing : this.renderForwardedIcon(config.variant)} </obc-readout-block> `; } /** * A cap-height `°` column whose width scales with the value size, rendered * after the value block. Setpoint / advice blocks render their own degree via * `obc-readout-block`. */ renderDegreeGlyph(size) { return html` <span class=${classMap({ \"degree-column\": true, [`degree-${size}`]: true, \"tone-enhanced\": this.rowEnhanced })} part=\"degree\" > <obc-textbox class=\"degree-glyph\" .size=${size} alignment=\"center\" >°</obc-textbox > </span> `; } /** * The column rendered after the value digits: * * - `hasDegree`: a cap-height `°` column whose width scales with the value size. * - `hasDegreeSpacer` (non-degree readouts): an invisible reserve of the same * width, so this readout's value digits stay aligned with degree readouts * in the same column. */ renderDegreeColumn(size = this.valueSize) { if ((this.hasDegree ?? false) && !this.off) { return this.renderDegreeGlyph(size); } if (this.hasDegreeSpacer ?? false) { return html`<span class=\"degree-spacer\" part=\"degree-spacer\" aria-hidden=\"true\" ></span>`; } return nothing; } renderTextbox(role, text, reserver, blockState) { const weight = role === \"label\" ? this.labelWeight : ObcTextboxFontWeight.regular; const size = role === \"label\" ? this.labelSize : ObcTextboxSize.xs; const box = html` <obc-textbox class=${classMap({ [role]: true, ...this.dataQualityClasses(blockState?.dataQuality) })} part=${role} .size=${size} .fontWeight=${weight} alignment=\"left\" > ${text} ${reserver ? html`<span slot=\"length\">${reserver}</span>` : nothing} </obc-textbox> `; return wrapWithAlertFrame(blockState?.alert ?? false, box); } get setpointHidePhase() { const popUpAtSetpoint = this.isPopUp && this.isAtSetpoint && !this.setpointTouching; return resolveSetpointHidePhase( popUpAtSetpoint, this.deferredSetpointHidePhase ); } // `hasDegree` is deliberately NOT forwarded to the advice / setpoint blocks: // the unit is written once per readout and the degree follows the same rule // (Figma 6.1). `obc-readout-list-item` keeps its own per-row convention. renderAdviceBlock() { return this.renderBlock({ variant: ReadoutBlockVariant.advice, value: this.advice, valueSize: this.secondarySize, enhanced: false, weight: ObcTextboxFontWeight.regular, hintedZeros: this.adviceOptions?.hintedZeros ?? false, hasSignSpacer: this.adviceOptions?.hasSignSpacer ?? false, spaceReserver: this.adviceOptions?.spaceReserver, dataQuality: this.adviceOptions?.dataQuality, alert: this.adviceOptions?.alert, category: this.adviceOptions?.category, active: this.adviceOptions?.active }); } renderSetpointBlock() { return this.renderBlock({ variant: ReadoutBlockVariant.setpoint, value: this.setpoint, valueSize: this.setpointSize, // Value and setpoint share the enhanced colour state (both neutral // or both enhanced); the setpoint is bold only while emphasised. enhanced: this.rowEnhanced, weight: this.setpointWeight, hintedZeros: this.setpointOptions?.hintedZeros ?? false, hasSignSpacer: this.setpointOptions?.hasSignSpacer ?? false, spaceReserver: this.setpointOptions?.spaceReserver, touching: this.setpointTouching, hidePhase: this.setpointHidePhase, dataQuality: this.setpointOptions?.dataQuality, alert: this.setpointOptions?.alert }); } /** * The value reading: the value block and its degree column (or spacer) * grouped in one relatively-positioned wrapper so the value alert frame AND * the value data-quality chip can wrap both together. Unlike * `obc-readout-list-item` the unit is not part of the reading — it lives in * the label/unit meta zone. */ renderValueReading() { return html` <div class=${classMap({ \"value-reading\": true, ...this.dataQualityClasses(this.valueOptions?.dataQuality) })} part=\"value-reading\" > ${this.renderBlock({ variant: ReadoutBlockVariant.value, value: this.value, valueType: this.valueType, valueSize: this.valueSize, enhanced: this.rowEnhanced, weight: this.valueWeight, hintedZeros: this.valueOptions?.hintedZeros ?? false, hasSignSpacer: this.valueOptions?.hasSignSpacer ?? false, spaceReserver: this.valueOptions?.spaceReserver, off: this.off, hasIcon: this.valueOptions?.hasIcon ?? false })} ${this.renderDegreeColumn()} ${this.renderValueAlertOverlay()} </div> `; } /** * The value alert frame, drawn as a pure overlay around the value reading * (value + degree). It reserves no space — the `obc-alert-frame` sits in an * absolutely-positioned box offset outward (see `.value-alert-overlay` in the * CSS) so its stroke is centred on the 4px/2px padding line and toggling it * never shifts content. Renders only when `valueOptions.alert` is a config * object. */ renderValueAlertOverlay() { const alert = this.valueOptions?.alert; if (typeof alert !== \"object\" || alert === null) { return nothing; } const thickness = alert.thickness ?? ObcAlertFrameThickness.Small; return html` <div class=\"value-alert-overlay\" aria-hidden=\"true\"> <obc-alert-frame part=\"value-alert-frame\" .type=${alert.type ?? ObcAlertFrameType.Regular} .thickness=${thickness} .status=${alert.status ?? AlertType.Alarm} .mode=${alert.mode ?? ObcAlertFrameMode.ackedActive} .flashingSpeed=${alert.flashingSpeed ?? FlashingSpeed.Default} .showIcon=${alert.showIcon ?? false} .showAlertCategoryIcon=${alert.showAlertCategoryIcon ?? true} .wrapContent=${false} .fullWidth=${false} ></obc-alert-frame> </div> `; } /** The label/unit meta zone; `stacking` picks the inline or stacked arrangement. */ renderMetaZone() { if (!this.label && !this.unit) { return nothing; } const stacked = this.isHorizontal || this.resolvedStacking === \"stacked\"; return html` <div class=${classMap({ meta: true, \"meta-inline\": !stacked, \"meta-stacked\": stacked, \"label-only\": !!this.label && !this.unit, \"unit-only\": !!this.unit && !this.label })} part=\"meta-wrapper\" > ${this.hasLeadingIcon ? html`<span class=\"leading-icon\" aria-hidden=\"true\" ><slot name=\"leading-icon\"></slot ></span>` : nothing} <div class=\"meta-labels\" part=\"meta-labels\"> ${this.label ? this.renderTextbox( \"label\", this.label, this.labelOptions?.spaceReserver ) : nothing} ${this.unit ? this.renderTextbox( \"unit\", this.unit, this.unitOptions?.spaceReserver ) : nothing} </div> </div> `; } renderSourceContent() { const interaction = this.resolvedSourceInteraction; const src = this.src ?? \"\"; const buttonClasses = classMap({ \"source-button\": true, ...this.dataQualityClasses(this.srcOptions?.dataQuality) }); if (interaction === \"picker\") { const button = html` <obc-button class=${buttonClasses} variant=\"flat\" .fullWidth=${false} showTrailingIcon @click=${() => { this.sourcePickerContentVisible = !this.sourcePickerContentVisible; }} > <obi-drop-down-google slot=\"trailing-icon\"></obi-drop-down-google> ${src} </obc-button> `; return wrapWithAlertFrame(this.srcOptions?.alert ?? false, button); } if (interaction === \"flyout\") { const button = html` <obc-button class=${buttonClasses} variant=\"flat\" .fullWidth=${false} showTrailingIcon @click=${() => { this.dispatchEvent( new CustomEvent(\"source-flyout-click\", { bubbles: true, composed: true, detail: { src } }) ); }} > <obi-chevron-right-google slot=\"trailing-icon\" ></obi-chevron-right-google> ${src} </obc-button> `; return wrapWithAlertFrame(this.srcOptions?.alert ?? false, button); } return this.renderSourceBlock(src); } /** * The plain (non-interactive) source: the textbox plus its optional state * chip and deviation line. The regular no-deviation case renders the bare * textbox; a state or deviation wraps it in the `.source-block` stack. The * picker / flyout button variants do not carry state / deviation yet — * TODO(designer): undefined in the 6.1 sheets. */ renderSourceBlock(src) { const state2 = this.srcOptions?.state ?? ReadoutSourceState.regular; const deviation = this.srcOptions?.deviation; const hasDeviation = deviation !== void 0 && Number.isFinite(deviation); const box = this.renderTextbox( \"source\", src, this.srcOptions?.spaceReserver, this.srcOptions ); if (state2 === ReadoutSourceState.regular && !hasDeviation) { return box; } return html` <div class=${classMap({ \"source-block\": true, [`source-state-${state2}`]: state2 !== ReadoutSourceState.regular })} part=\"source-block\" > ${box} ${hasDeviation ? html`<span class=\"source-deviation\"> <obi-delta aria-hidden=\"true\"></obi-delta> <obc-textbox class=\"source-deviation-value\" .size=${ObcTextboxSize.xs} .tabularNums=${true} alignment=\"left\" >${deviation}</obc-textbox > </span>` : nothing} </div> `; } /** * The source row (vertical) / segment (horizontal). Only the horizontal * direction draws an auto divider before the source — the Figma 6.1 review * (node 95:24490 \"remove dividers\", 95:26891 \"Source-divider are there\") * keeps the vertical source row divider-free while the horizontal flow * separates the source with a vertical rule. */ renderSource() { if (!this.hasSrc) { return nothing; } return html` ${this.isHorizontal ? html`<div class=\"divider divider-vertical\" part=\"divider\" aria-hidden=\"true\" ></div>` : nothing} <div class=\"source-row\" part=\"source-wrapper\"> ${this.renderSourceContent()} </div> `; } // ---------- Source picker (context menu built from src-picker-content) ---------- getSourcePickerNavigationItems() { const assignedElements = this.sourcePickerSlot?.assignedElements({ flatten: true }) ?? []; return assignedElements.flatMap((element) => { if (!(element instanceof HTMLElement)) { return []; } if (element.localName === \"obc-navigation-item\") { return [element]; } return Array.from( element.querySelectorAll(\"obc-navigation-item\") ); }); } createSourcePickerOptionIcon(element) { const iconElement = element.querySelector('[slot=\"icon\"]'); if (!(iconElement instanceof HTMLElement)) { return void 0; } return html`${iconElement.cloneNode(true)}`; } getSourcePickerItemInfo(item, index) { const itemWithValues = item; const itemLabel = itemWithValues.label || item.getAttribute(\"label\") || \"\"; const itemValue = item.getAttribute(\"data-value\") || itemWithValues.value || item.getAttribute(\"value\") || itemLabel || `source-option-${index}`; return { itemLabel, itemValue }; } findSourcePickerOptionElement(value) { const navigationItems = this.getSourcePickerNavigationItems(); for (const [index, item] of navigationItems.entries()) { const { itemValue } = this.getSourcePickerItemInfo(item, index); if (itemValue === value) { return item; } } return void 0; } handleSourcePickerItemClick(event) { this.dispatchEvent( new CustomEvent(\"source-change\", { bubbles: true, composed: true, detail: { value: event.detail.value, label: event.detail.option?.label } }) ); this.findSourcePickerOptionElement(event.detail.value)?.click(); this.sourcePickerContentVisible = false; } /** * The context menu highlights options by their VALUE, while `src` holds the * displayed source text — which may be an option's label when the two * differ (e.g. `data-value=\"gps-1\"` with label \"GPS 1\"). Resolve the current * source to the matching option's value (by value or label) so the open * picker always highlights the active source. */ get selectedSourceValues() { if (!this.src) { return []; } const match = this.sourcePickerOptions.find( (option) => option.value === this.src || option.label === this.src ); return [match?.value ?? this.src]; } renderSourcePickerContent() { if (this.resolvedSourceInteraction !== \"picker\" || !this.sourcePickerContentVisible) { return nothing; } return html` <obc-context-menu-input .type=${ContextMenuType.Regular} .options=${this.sourcePickerOptions} .selectedValues=${this.selectedSourceValues} class=\"source-picker-content\" @item-click=${this.handleSourcePickerItemClick} @close=${() => { this.sourcePickerContentVisible = false; }} ></obc-context-menu-input> `; } renderSourcePickerSlot() { return html` <slot name=\"src-picker-content\" hidden @slotchange=${this.syncSourcePickerOptions} ></slot> `; } // ---------- Layouts ---------- /** * An invisible primary-size duplicate of a dynamic setpoint row's content, * stacked under the visible block in the same grid cell purely to reserve * width. With both rows permanently holding their emphasised width, the * widest row — and therefore the readout's total width — stays constant * while a flip-flop swaps the value/setpoint sizes, while a touch swap * runs, and while a pop-up setpoint fades out, so the meta zone and source * never shift. This is the vertical analogue of the horizontal arrangement, * where the swapped pair sits on one row and its summed width is constant * by construction (the designer's flip-flop invariant). Only the vertical * direction needs it; `always-visible` layouts are untouched. */ renderSetpointWidthReserve(kind) { if (!this.hasDynamicSetpoint || this.isHorizontal) { return nothing; } const content = kind === \"setpoint\" ? this.renderBlock({ variant: ReadoutBlockVariant.setpoint, value: this.setpoint, valueSize: this.primarySize, enhanced: false, // The emphasised setpoint is SemiBold — reserve its widest form. // No degree: the visible setpoint block carries none (the degree // renders on the actual value only), so the ghost mirrors it. weight: ObcTextboxFontWeight.semibold, hintedZeros: this.setpointOptions?.hintedZeros ?? false, hasSignSpacer: this.setpointOptions?.hasSignSpacer ?? false, spaceReserver: this.setpointOptions?.spaceReserver, alert: this.setpointOptions?.alert, ghost: true }) : html`${this.renderBlock({ variant: ReadoutBlockVariant.value, value: this.value, valueType: this.valueType, valueSize: this.primarySize, enhanced: false, weight: this.valueWeight, hintedZeros: this.valueOptions?.hintedZeros ?? false, hasSignSpacer: this.valueOptions?.hasSignSpacer ?? false, spaceReserver: this.valueOptions?.spaceReserver, off: this.off, hasIcon: this.valueOptions?.hasIcon ?? false, ghost: true })}${this.renderDegreeColumn(this.primarySize)}`; return html`<div class=\"setpoint-width-reserve\" aria-hidden=\"true\"> ${content} </div>`; } /** * Vertical: advice / setpoint / value rows right-aligned in a cluster, the * label+unit meta zone below, then the source row under an auto divider. * `alignment` re-aligns the whole column for stacked readouts. */ renderVerticalLayout() { return html` <div class=\"value-cluster\" part=\"value-cluster\"> ${this.hasAdvice ? html`<div class=\"advice-row\" part=\"advice-wrapper\"> ${this.renderAdviceBlock()} </div>` : nothing} ${this.hasSetpoint ? html`<div class=\"setpoint-row\" part=\"setpoint-wrapper\"> ${this.renderSetpointBlock()} ${this.renderSetpointWidthReserve(\"setpoint\")} </div>` : nothing} ${this.hasValue ? html`<div class=\"value-row\" part=\"value-wrapper\"> ${this.renderValueReading()} ${this.renderSetpointWidthReserve(\"value\")} </div>` : nothing} </div> ${this.renderMetaZone()} ${this.renderSource()} `; } /** * Horizontal: advice, setpoint, value and the stacked label/unit meta flow on * one cap-height-aligned row, with the source at the end after an auto * divider. */ renderHorizontalLayout() { return html` <div class=\"inline-row\" part=\"value-cluster\"> ${this.hasAdvice ? html`<div class=\"advice-row\" part=\"advice-wrapper\"> ${this.renderAdviceBlock()} </div>` : nothing} ${this.hasSetpoint ? html`<div class=\"setpoint-row\" part=\"setpoint-wrapper\"> ${this.renderSetpointBlock()} </div>` : nothing} ${this.hasValue ? html`<div class=\"value-row\" part=\"value-wrapper\"> ${this.renderValueReading()} </div>` : nothing} ${this.renderMetaZone()} ${this.renderSource()} </div> `; } willUpdate(changed) { super.willUpdate(changed); assertReadoutValueType(\"obc-readout\", this.value, this.valueType); assertReadoutFractionDigits(\"obc-readout\", this.fractionDigits); this.warnHorizontalSizeIgnored(); } /** * A horizontal readout discards any `size` but `large` (see `resolvedSize`). * A silent discard is indistinguishable from a bug to a consumer, so each * offending element says so once and logs itself for locating (#1182). * A removed `size` attribute arrives as `null` and counts as unset. */ warnHorizontalSizeIgnored() { if (this.hasWarnedHorizontalSize || !this.isHorizontal || !this.size || this.size === ReadoutSize.large) { return; } this.hasWarnedHorizontalSize = true; console.warn( `[obc-readout] size=\"${this.size}\" is ignored when direction=\"horizontal\": the horizontal arrangement exists in the large tier only. Remove the size, or use direction=\"vertical\" for the small / medium tiers.`, this ); } updated(changed) { super.updated(changed); if (changed.has(\"sourcePickerContentVisible\")) { if (this.sourcePickerContentVisible) { window.addEventListener(\"pointerdown\", this.onWindowPointerDown, true); } else { window.removeEventListener( \"pointerdown\", this.onWindowPointerDown, true ); } } const firstUpdate = !this.hasCompletedFirstUpdate; this.hasCompletedFirstUpdate = true; if (!this.isPopUp || this.setpointTouching) { this.clearDeferredSetpointHide(); return; } const shouldHide = this.hasSetpoint && this.isAtSetpoint; if (firstUpdate) { this.deferredSetpointHidePhase = shouldHide ? ReadoutBlockHidePhase.hidden : ReadoutBlockHidePhase.none; return; } if (!shouldHide) { this.clearDeferredSetpointHide(); return; } if (this.deferredSetpointHidePhase !== ReadoutBlockHidePhase.none) { return; } this.deferredSetpointHidePhase = ReadoutBlockHidePhase.hiding; window.clearTimeout(this.deferredSetpointHideTimer); this.deferredSetpointHideTimer = window.setTimeout(() => { this.deferredSetpointHidePhase = ReadoutBlockHidePhase.hidden; this.deferredSetpointHideTimer = void 0; }, 100); } clearDeferredSetpointHide() { if (this.deferredSetpointHidePhase !== ReadoutBlockHidePhase.none) { this.deferredSetpointHidePhase = ReadoutBlockHidePhase.none; } window.clearTimeout(this.deferredSetpointHideTimer); this.deferredSetpointHideTimer = void 0; } disconnectedCallback() { window.removeEventListener(\"pointerdown\", this.onWindowPointerDown, true); window.clearTimeout(this.deferredSetpointHideTimer); this.deferredSetpointHideTimer = void 0; if (this.deferredSetpointHidePhase === ReadoutBlockHidePhase.hiding) { this.deferredSetpointHidePhase = ReadoutBlockHidePhase.hidden; } super.disconnectedCallback(); } render() { const dataQuality = this.dataQuality; const classes = classMap({ readout: true, [`size-${this.resolvedSize}`]: true, [`direction-${this.resolvedDirection}`]: true, [`stacking-${this.resolvedStacking}`]: true, [`alignment-${this.resolvedAlignment}`]: true, [`priority-${this.resolvedPriority}`]: true, \"data-low-integrity\": dataQuality === ReadoutDataQuality.lowIntegrity, \"data-invalid\": dataQuality === ReadoutDataQuality.invalid, \"flip-flop\": this.isFlipFlop, \"setpoint-dynamic\": this.hasDynamicSetpoint }); const root = html` <div class=${classes} part=\"root\"> ${this.isHorizontal ? this.renderHorizontalLayout() : this.renderVerticalLayout()} ${this.renderSourcePickerSlot()} </div> ${this.renderSourcePickerContent()} `; const alert = this.alert === true ? {} : this.alert; return wrapWithAlertFrame(alert, root); } }"
|
|
10286
10348
|
}
|
|
10287
10349
|
],
|
|
10288
10350
|
"exports": [
|
|
@@ -48756,18 +48818,210 @@
|
|
|
48756
48818
|
},
|
|
48757
48819
|
{
|
|
48758
48820
|
"kind": "javascript-module",
|
|
48759
|
-
"path": "src/components/alert-button/alert-button.ts",
|
|
48821
|
+
"path": "src/components/alert-button-item/alert-button-item.ts",
|
|
48760
48822
|
"declarations": [
|
|
48761
48823
|
{
|
|
48762
48824
|
"kind": "class",
|
|
48763
|
-
"description": "`<obc-alert-button>` –
|
|
48764
|
-
"name": "
|
|
48765
|
-
"
|
|
48825
|
+
"description": "`<obc-alert-button-item>` – The bell button that shows the alert state and count.\n\nShows the alert status as a bell in the style of the given alert type, with\nan optional counter, or as a global counter: one badge per severity, then\nthe bell and the total. `obc-alert-button` renders the item next to its\nsilence button; use the item on its own where no silence button is needed.\n\n---\n\n### Features\n- **Variants (`type`):** `flat` (icon only), `normal` (bordered, icon and\n counter) and `enhanced` (filled in the severity colour).\n- **Alert types:** `alarm`, `warning`, `caution` and the `level-*` family;\n `nAlerts` of 0 shows the idle bell.\n- **Counter:** `counter` shows `nAlerts` next to the bell, except in `flat`.\n- **Global counter:** `globalCounter` shows the per-severity `counts` as\n badges, then the bell and `nAlerts`, always in the normal style.\n- **Flashing:** `blinking` flashes the bell at the tempo `flashingSpeed`\n resolves (#1224).\n- **Fill height:** `fillHeight` stretches the visible button to the host\n height, as the tall `obc-alert-button` does.\n\n---\n\n### Usage Guidelines\n- Keep `nAlerts` and `counts` consistent; the item shows both as given.\n- The generated label reads e.g. \"Alerts, 12, 2 Alarm, 4 Warning,\n 6 Caution\"; an `aria-label` on the host replaces it.\n- A native button, so Enter and Space activate it (APG button pattern).\n- A parent that joins another button to the item's end sets the\n `data-group-item-not-last` attribute on the host.\n- **TODO(designer):** the Figma file draws the flat item's hover, active and\n focused variants at 20 % opacity; the item uses the standard flat states.\n- **TODO(designer):** the Figma file defines no flashing for the global\n counter, so it never flashes.\n\n---\n\n### Example\n\n```html\n<obc-alert-button-item alertType=\"alarm\" nAlerts=\"3\" counter blinking></obc-alert-button-item>\n<obc-alert-button-item\n globalCounter\n nAlerts=\"12\"\n .counts=${{countAlarm: 2, countWarning: 4, countCaution: 6}}\n></obc-alert-button-item>\n```",
|
|
48826
|
+
"name": "ObcAlertButtonItem",
|
|
48827
|
+
"members": [
|
|
48766
48828
|
{
|
|
48767
|
-
"
|
|
48768
|
-
"name": ""
|
|
48829
|
+
"kind": "field",
|
|
48830
|
+
"name": "type",
|
|
48831
|
+
"type": {
|
|
48832
|
+
"text": "ObcAlertButtonType"
|
|
48833
|
+
},
|
|
48834
|
+
"description": "Visual variant: `flat` (icon only), `normal` (default, bordered with the counter) or `enhanced` (filled in the severity colour)."
|
|
48835
|
+
},
|
|
48836
|
+
{
|
|
48837
|
+
"kind": "field",
|
|
48838
|
+
"name": "alertType",
|
|
48839
|
+
"type": {
|
|
48840
|
+
"text": "AlertType | undefined"
|
|
48841
|
+
},
|
|
48842
|
+
"description": "Alert type that selects the icon and colours: `alarm`, `warning`, `caution` or a `level-*` severity."
|
|
48843
|
+
},
|
|
48844
|
+
{
|
|
48845
|
+
"kind": "field",
|
|
48846
|
+
"name": "nAlerts",
|
|
48847
|
+
"type": {
|
|
48848
|
+
"text": "number"
|
|
48849
|
+
},
|
|
48850
|
+
"default": "0",
|
|
48851
|
+
"description": "Number of active alerts; 0 shows the idle bell and hides the counter."
|
|
48852
|
+
},
|
|
48853
|
+
{
|
|
48854
|
+
"kind": "field",
|
|
48855
|
+
"name": "counter",
|
|
48856
|
+
"type": {
|
|
48857
|
+
"text": "boolean"
|
|
48858
|
+
},
|
|
48859
|
+
"default": "false",
|
|
48860
|
+
"description": "Shows `nAlerts` next to the bell (not in `flat`)."
|
|
48861
|
+
},
|
|
48862
|
+
{
|
|
48863
|
+
"kind": "field",
|
|
48864
|
+
"name": "globalCounter",
|
|
48865
|
+
"type": {
|
|
48866
|
+
"text": "boolean"
|
|
48867
|
+
},
|
|
48868
|
+
"default": "false",
|
|
48869
|
+
"description": "Shows the per-severity counts, the bell and the total instead of a single alert status."
|
|
48870
|
+
},
|
|
48871
|
+
{
|
|
48872
|
+
"kind": "field",
|
|
48873
|
+
"name": "counts",
|
|
48874
|
+
"type": {
|
|
48875
|
+
"text": "AlertCounts"
|
|
48876
|
+
},
|
|
48877
|
+
"default": "{}",
|
|
48878
|
+
"description": "Alert count per severity for the global counter badges."
|
|
48879
|
+
},
|
|
48880
|
+
{
|
|
48881
|
+
"kind": "field",
|
|
48882
|
+
"name": "shelvedCount",
|
|
48883
|
+
"type": {
|
|
48884
|
+
"text": "number"
|
|
48885
|
+
},
|
|
48886
|
+
"default": "0",
|
|
48887
|
+
"description": "Number of shelved alerts, shown after the global counter badges."
|
|
48888
|
+
},
|
|
48889
|
+
{
|
|
48890
|
+
"kind": "field",
|
|
48891
|
+
"name": "blinking",
|
|
48892
|
+
"type": {
|
|
48893
|
+
"text": "boolean"
|
|
48894
|
+
},
|
|
48895
|
+
"default": "false",
|
|
48896
|
+
"description": "Flashes the bell while there are active alerts of a flashing alert type."
|
|
48897
|
+
},
|
|
48898
|
+
{
|
|
48899
|
+
"kind": "field",
|
|
48900
|
+
"name": "flashingSpeed",
|
|
48901
|
+
"type": {
|
|
48902
|
+
"text": "FlashingSpeed"
|
|
48903
|
+
},
|
|
48904
|
+
"description": "Flash tempo while `blinking` is on: `default` resolves from `alertType`, `fast`, `slow`, `very-slow` force a tempo, `fixed` never flashes."
|
|
48905
|
+
},
|
|
48906
|
+
{
|
|
48907
|
+
"kind": "field",
|
|
48908
|
+
"name": "fillHeight",
|
|
48909
|
+
"type": {
|
|
48910
|
+
"text": "boolean"
|
|
48911
|
+
},
|
|
48912
|
+
"default": "false",
|
|
48913
|
+
"description": "Stretches the visible button to the host height instead of the 32 px visual target."
|
|
48914
|
+
},
|
|
48915
|
+
{
|
|
48916
|
+
"kind": "field",
|
|
48917
|
+
"name": "ariaLabel",
|
|
48918
|
+
"type": {
|
|
48919
|
+
"text": "string | null"
|
|
48920
|
+
},
|
|
48921
|
+
"default": "null",
|
|
48922
|
+
"description": "Accessible name forwarded to the inner `<button>`, mapped to the `aria-label` attribute; replaces the generated label. `aria-labelledby` is not supported: ID references cannot cross the shadow boundary."
|
|
48923
|
+
},
|
|
48924
|
+
{
|
|
48925
|
+
"kind": "field",
|
|
48926
|
+
"name": "flashing",
|
|
48927
|
+
"privacy": "protected",
|
|
48928
|
+
"readonly": true,
|
|
48929
|
+
"default": "new FlashingController( this, () => this.resolvedFlashingSpeed )"
|
|
48930
|
+
},
|
|
48931
|
+
{
|
|
48932
|
+
"kind": "field",
|
|
48933
|
+
"name": "resolvedFlashingSpeed",
|
|
48934
|
+
"type": {
|
|
48935
|
+
"text": "ResolvedFlashingSpeed"
|
|
48936
|
+
},
|
|
48937
|
+
"readonly": true
|
|
48938
|
+
},
|
|
48939
|
+
{
|
|
48940
|
+
"kind": "field",
|
|
48941
|
+
"name": "accessibleName",
|
|
48942
|
+
"type": {
|
|
48943
|
+
"text": "string"
|
|
48944
|
+
},
|
|
48945
|
+
"privacy": "private",
|
|
48946
|
+
"readonly": true
|
|
48947
|
+
},
|
|
48948
|
+
{
|
|
48949
|
+
"kind": "method",
|
|
48950
|
+
"name": "renderTwotoneIcon",
|
|
48951
|
+
"privacy": "private"
|
|
48952
|
+
},
|
|
48953
|
+
{
|
|
48954
|
+
"kind": "method",
|
|
48955
|
+
"name": "renderIcon",
|
|
48956
|
+
"privacy": "private",
|
|
48957
|
+
"parameters": [
|
|
48958
|
+
{
|
|
48959
|
+
"name": "styleType",
|
|
48960
|
+
"type": {
|
|
48961
|
+
"text": "ObcAlertButtonType"
|
|
48962
|
+
}
|
|
48963
|
+
},
|
|
48964
|
+
{
|
|
48965
|
+
"name": "blinkLayer",
|
|
48966
|
+
"type": {
|
|
48967
|
+
"text": "boolean"
|
|
48968
|
+
}
|
|
48969
|
+
}
|
|
48970
|
+
],
|
|
48971
|
+
"description": "The blink layer shows the opposite of the steady layer in each flash."
|
|
48972
|
+
},
|
|
48973
|
+
{
|
|
48974
|
+
"kind": "method",
|
|
48975
|
+
"name": "renderCount",
|
|
48976
|
+
"privacy": "private"
|
|
48977
|
+
},
|
|
48978
|
+
{
|
|
48979
|
+
"kind": "method",
|
|
48980
|
+
"name": "renderCounts",
|
|
48981
|
+
"privacy": "private"
|
|
48982
|
+
},
|
|
48983
|
+
{
|
|
48984
|
+
"kind": "method",
|
|
48985
|
+
"name": "render"
|
|
48986
|
+
},
|
|
48987
|
+
{
|
|
48988
|
+
"kind": "field",
|
|
48989
|
+
"name": "styles",
|
|
48990
|
+
"static": true
|
|
48991
|
+
}
|
|
48992
|
+
],
|
|
48993
|
+
"events": [
|
|
48994
|
+
{
|
|
48995
|
+
"description": "Fired when the button is clicked.",
|
|
48996
|
+
"name": "click"
|
|
48769
48997
|
}
|
|
48770
48998
|
],
|
|
48999
|
+
"superclass": {
|
|
49000
|
+
"name": "LitElement",
|
|
49001
|
+
"package": "lit"
|
|
49002
|
+
},
|
|
49003
|
+
"customElement": true
|
|
49004
|
+
}
|
|
49005
|
+
],
|
|
49006
|
+
"exports": [
|
|
49007
|
+
{
|
|
49008
|
+
"kind": "js",
|
|
49009
|
+
"name": "ObcAlertButtonItem",
|
|
49010
|
+
"declaration": {
|
|
49011
|
+
"name": "ObcAlertButtonItem",
|
|
49012
|
+
"module": "src/components/alert-button-item/alert-button-item.ts"
|
|
49013
|
+
}
|
|
49014
|
+
}
|
|
49015
|
+
]
|
|
49016
|
+
},
|
|
49017
|
+
{
|
|
49018
|
+
"kind": "javascript-module",
|
|
49019
|
+
"path": "src/components/alert-button/alert-button.ts",
|
|
49020
|
+
"declarations": [
|
|
49021
|
+
{
|
|
49022
|
+
"kind": "class",
|
|
49023
|
+
"description": "`<obc-alert-button>` – A compact, icon-based button for displaying alert or notification status and count.\n\nThis component provides a visual indicator for active alerts or notifications, supporting different alert types (such as alarm, warning, or caution) and an optional counter badge. It can also include a secondary \"silence\" action button for muting alerts, adapting its layout responsively for different screen widths.\n\nAppears in toolbars or notification areas to give users a quick overview of alert status and provide direct access to alert-related actions. The bell itself is an `obc-alert-button-item`; use the item directly where no silence button or breakpoints are needed, or for its global counter.\n\n## Features\n\n- **Variants:**\n - **Flat:** Minimal, icon-only button for space-constrained layouts.\n - **Normal:** Standard button with icon and optional alert counter.\n - **Enhanced:** Visually prominent button for high-priority alerts, with accent styling.\n- **Alert Types:** Supports `alarm`, `warning`, `caution`, or no alert (idle). Icon and color adapt to the alert type.\n- **Alert Counter:** Optional badge displays the number of active alerts (except in flat mode).\n- **Global Counter:** `globalCounter` shows the per-severity `counts` and an optional `shelvedCount` before the bell and the total, as `obc-alert-button-item` draws them.\n- **Silence Button:** Optional secondary button to mute or silence alerts, shown when enabled and at sufficient width.\n- **Blinking Animation:** Can animate (blink) to draw attention to active alerts (except for caution type).\n- **Responsive Layout:** Automatically switches to flat mode below a configurable width, and hides the silence button below another configurable width.\n- **Large Size Option:** Increases button height and padding for touch-friendly or prominent use.\n\n## Usage Guidelines\n\nUse `<obc-alert-button>` to provide a persistent, easily accessible indicator of alert or notification status. Ideal for toolbars, headers, or notification panels where users need to be aware of active alerts and may need to silence them quickly.\n\n- Use the counter to show the number of active alerts when relevant.\n- Use the silence button to provide a quick mute action, but only when space allows.\n- Choose the variant (`flat`, `normal`, `enhanced`) based on available space and the importance of the alert.\n- The blinking feature should be reserved for urgent or high-priority alerts to avoid unnecessary distraction.\n\n**TODO(designer):** Confirm if there are recommended default behaviors for auto-blinking, and if there are any design constraints for when to use each variant.\n\n**TODO(designer):** The Figma Alert button has no flat Global counter, so below `flatMaxBreakpointPx` the button shows the flat bell without the counts.\n\n## Events\n\n- `click-alert` – Fired when the main alert button is clicked.\n- `click-silence` – Fired when the silence button is clicked.\n\n## Best Practices & Constraints\n\n- Only show the counter when there are active alerts and the button is not in flat mode.\n- The silence button is only visible if enabled, the width is above the minimum breakpoint, and the button is not flat.\n- Blinking should be used sparingly to avoid overwhelming the user.\n- Use the large variant for touch interfaces or when the button needs to stand out.\n\n## Example\n\n```html\n<obc-alert-button\n nAlerts=\"3\"\n alertType=\"alarm\"\n type=\"enhanced\"\n counter\n showSilenceButton\n blinking\n large\n flatMaxBreakpointPx=\"600\"\n silenceButtonMinBreakpointPx=\"600\"\n></obc-alert-button>\n```\n\nIn this example, the button shows an alarm icon, a counter badge with \"3\", is styled as enhanced, blinks to indicate urgency, and includes a silence button if the width allows.",
|
|
49024
|
+
"name": "ObcAlertButton",
|
|
48771
49025
|
"members": [
|
|
48772
49026
|
{
|
|
48773
49027
|
"kind": "field",
|
|
@@ -48776,7 +49030,7 @@
|
|
|
48776
49030
|
"text": "number"
|
|
48777
49031
|
},
|
|
48778
49032
|
"default": "0",
|
|
48779
|
-
"description": "Number of active alerts
|
|
49033
|
+
"description": "Number of active alerts shown in the counter; 0 shows the idle state."
|
|
48780
49034
|
},
|
|
48781
49035
|
{
|
|
48782
49036
|
"kind": "field",
|
|
@@ -48784,12 +49038,12 @@
|
|
|
48784
49038
|
"type": {
|
|
48785
49039
|
"text": "AlertType | undefined"
|
|
48786
49040
|
},
|
|
48787
|
-
"description": "
|
|
49041
|
+
"description": "Alert type that selects the icon and colours: `alarm`, `warning`, `caution`, a `level-*` severity, or unset for the idle state."
|
|
48788
49042
|
},
|
|
48789
49043
|
{
|
|
48790
49044
|
"kind": "field",
|
|
48791
49045
|
"name": "type",
|
|
48792
|
-
"description": "Visual variant
|
|
49046
|
+
"description": "Visual variant: `flat` (icon only), `normal` (default, icon and optional counter) or `enhanced` (emphasized for high-priority alerts)."
|
|
48793
49047
|
},
|
|
48794
49048
|
{
|
|
48795
49049
|
"kind": "field",
|
|
@@ -48798,7 +49052,7 @@
|
|
|
48798
49052
|
"text": "boolean"
|
|
48799
49053
|
},
|
|
48800
49054
|
"default": "false",
|
|
48801
|
-
"description": "Increases button
|
|
49055
|
+
"description": "Increases the button height and padding for touch-friendly or prominent use."
|
|
48802
49056
|
},
|
|
48803
49057
|
{
|
|
48804
49058
|
"kind": "field",
|
|
@@ -48807,7 +49061,7 @@
|
|
|
48807
49061
|
"text": "boolean"
|
|
48808
49062
|
},
|
|
48809
49063
|
"default": "false",
|
|
48810
|
-
"description": "
|
|
49064
|
+
"description": "Shows the silence button when the width is at least `silenceButtonMinBreakpointPx` and the button is not flat."
|
|
48811
49065
|
},
|
|
48812
49066
|
{
|
|
48813
49067
|
"kind": "field",
|
|
@@ -48825,39 +49079,51 @@
|
|
|
48825
49079
|
"text": "boolean"
|
|
48826
49080
|
},
|
|
48827
49081
|
"default": "false",
|
|
48828
|
-
"description": "
|
|
49082
|
+
"description": "Shows the alert counter when there are active alerts and the button is not flat."
|
|
48829
49083
|
},
|
|
48830
49084
|
{
|
|
48831
49085
|
"kind": "field",
|
|
48832
|
-
"name": "
|
|
49086
|
+
"name": "globalCounter",
|
|
48833
49087
|
"type": {
|
|
48834
49088
|
"text": "boolean"
|
|
48835
49089
|
},
|
|
48836
49090
|
"default": "false",
|
|
48837
|
-
"description": "
|
|
49091
|
+
"description": "Shows the per-severity counts and the shelved count before the bell and the total (the item's global counter); a flat button shows the plain bell."
|
|
48838
49092
|
},
|
|
48839
49093
|
{
|
|
48840
49094
|
"kind": "field",
|
|
48841
|
-
"name": "
|
|
49095
|
+
"name": "counts",
|
|
48842
49096
|
"type": {
|
|
48843
|
-
"text": "
|
|
49097
|
+
"text": "AlertCounts"
|
|
48844
49098
|
},
|
|
48845
|
-
"
|
|
49099
|
+
"default": "{}",
|
|
49100
|
+
"description": "Alert count per severity for the global counter badges (`countAlarm`, `countWarning`, `countCaution` and the `level-*` counts)."
|
|
48846
49101
|
},
|
|
48847
49102
|
{
|
|
48848
49103
|
"kind": "field",
|
|
48849
|
-
"name": "
|
|
48850
|
-
"
|
|
48851
|
-
|
|
48852
|
-
|
|
49104
|
+
"name": "shelvedCount",
|
|
49105
|
+
"type": {
|
|
49106
|
+
"text": "number"
|
|
49107
|
+
},
|
|
49108
|
+
"default": "0",
|
|
49109
|
+
"description": "Number of shelved alerts, shown after the global counter badges; hidden at zero."
|
|
48853
49110
|
},
|
|
48854
49111
|
{
|
|
48855
49112
|
"kind": "field",
|
|
48856
|
-
"name": "
|
|
49113
|
+
"name": "blinking",
|
|
48857
49114
|
"type": {
|
|
48858
|
-
"text": "
|
|
49115
|
+
"text": "boolean"
|
|
48859
49116
|
},
|
|
48860
|
-
"
|
|
49117
|
+
"default": "false",
|
|
49118
|
+
"description": "Flashes the bell for active alerts of a flashing alert type (never for caution)."
|
|
49119
|
+
},
|
|
49120
|
+
{
|
|
49121
|
+
"kind": "field",
|
|
49122
|
+
"name": "flashingSpeed",
|
|
49123
|
+
"type": {
|
|
49124
|
+
"text": "FlashingSpeed"
|
|
49125
|
+
},
|
|
49126
|
+
"description": "Flash tempo while `blinking` is on: `default` resolves from `alertType`, `fast`, `slow`, `very-slow` force a tempo, `fixed` never flashes."
|
|
48861
49127
|
},
|
|
48862
49128
|
{
|
|
48863
49129
|
"kind": "field",
|
|
@@ -48866,7 +49132,7 @@
|
|
|
48866
49132
|
"text": "number"
|
|
48867
49133
|
},
|
|
48868
49134
|
"default": "0",
|
|
48869
|
-
"description": "
|
|
49135
|
+
"description": "Window width in px below which a `normal` or `enhanced` button switches to flat mode."
|
|
48870
49136
|
},
|
|
48871
49137
|
{
|
|
48872
49138
|
"kind": "field",
|
|
@@ -48875,7 +49141,7 @@
|
|
|
48875
49141
|
"text": "number"
|
|
48876
49142
|
},
|
|
48877
49143
|
"default": "0",
|
|
48878
|
-
"description": "Minimum width
|
|
49144
|
+
"description": "Minimum window width in px for showing the silence button."
|
|
48879
49145
|
},
|
|
48880
49146
|
{
|
|
48881
49147
|
"kind": "field",
|
|
@@ -48887,21 +49153,6 @@
|
|
|
48887
49153
|
"name": "resizeListener",
|
|
48888
49154
|
"privacy": "private"
|
|
48889
49155
|
},
|
|
48890
|
-
{
|
|
48891
|
-
"kind": "method",
|
|
48892
|
-
"name": "renderAlertTwotoneIcon",
|
|
48893
|
-
"privacy": "private"
|
|
48894
|
-
},
|
|
48895
|
-
{
|
|
48896
|
-
"kind": "method",
|
|
48897
|
-
"name": "alertIcon",
|
|
48898
|
-
"privacy": "private"
|
|
48899
|
-
},
|
|
48900
|
-
{
|
|
48901
|
-
"kind": "method",
|
|
48902
|
-
"name": "alertIconNegative",
|
|
48903
|
-
"privacy": "private"
|
|
48904
|
-
},
|
|
48905
49156
|
{
|
|
48906
49157
|
"kind": "field",
|
|
48907
49158
|
"name": "activeType",
|
|
@@ -48954,6 +49205,14 @@
|
|
|
48954
49205
|
}
|
|
48955
49206
|
],
|
|
48956
49207
|
"exports": [
|
|
49208
|
+
{
|
|
49209
|
+
"kind": "js",
|
|
49210
|
+
"name": "ObcAlertButtonType",
|
|
49211
|
+
"declaration": {
|
|
49212
|
+
"name": "ObcAlertButtonType",
|
|
49213
|
+
"module": "src/components/alert-button/alert-button.ts"
|
|
49214
|
+
}
|
|
49215
|
+
},
|
|
48957
49216
|
{
|
|
48958
49217
|
"kind": "js",
|
|
48959
49218
|
"name": "ObcAlertButton",
|
|
@@ -48964,6 +49223,69 @@
|
|
|
48964
49223
|
}
|
|
48965
49224
|
]
|
|
48966
49225
|
},
|
|
49226
|
+
{
|
|
49227
|
+
"kind": "javascript-module",
|
|
49228
|
+
"path": "src/components/alert-counter-item/alert-counter-item.ts",
|
|
49229
|
+
"declarations": [
|
|
49230
|
+
{
|
|
49231
|
+
"kind": "class",
|
|
49232
|
+
"description": "`<obc-alert-counter-item>` – A row of alert counts, one badge per severity.\n\nRenders a large `obc-badge` for every severity with a count above zero, most\nsevere first, followed by an optional shelved count. Nothing renders when\nnothing is counted.\n\n---\n\n### Features\n- **Two types:** `badges` draws filled badges in the severity colour;\n `alert-level` draws flat badges with the severity glyph.\n- **Every severity:** `counts` takes the whole `AlertType` vocabulary,\n including the `level-*` family, ordered by `ALERT_SEVERITY_PRIORITY`.\n- **Shelved count:** `shelvedCount` adds a neutral badge at the end, with\n the shelf icon in `alert-level`.\n- **Accessible summary:** the row is announced as one image labelled with\n the counts, e.g. \"2 Alarm, 4 Warning, 6 Caution\".\n\n---\n\n### Usage Guidelines\n- Show how many alerts are outstanding per severity in a compact space;\n `obc-alert-button-item` nests the counter for its global counter.\n- Pass the counts as one object: `.counts=${{countAlarm: 2, countWarning: 4}}`.\n- The counter is display-only: it renders no button and fires no events.\n- For a framed per-subsystem summary with a label, see\n `obc-alert-subsystem-counter`.\n- **TODO(designer):** the Figma component also has an Unacked counter type;\n how its icons map to counts is not defined yet.\n\n---\n\n### Example\n\n```html\n<obc-alert-counter-item\n .counts=${{countAlarm: 2, countWarning: 4, countCaution: 6}}\n shelvedCount=\"9\"\n></obc-alert-counter-item>\n```",
|
|
49233
|
+
"name": "ObcAlertCounterItem",
|
|
49234
|
+
"members": [
|
|
49235
|
+
{
|
|
49236
|
+
"kind": "field",
|
|
49237
|
+
"name": "type",
|
|
49238
|
+
"type": {
|
|
49239
|
+
"text": "ObcAlertCounterItemType"
|
|
49240
|
+
},
|
|
49241
|
+
"description": "How each count is drawn: `badges` (filled severity badges, default) or `alert-level` (flat badges with the severity glyph)."
|
|
49242
|
+
},
|
|
49243
|
+
{
|
|
49244
|
+
"kind": "field",
|
|
49245
|
+
"name": "counts",
|
|
49246
|
+
"type": {
|
|
49247
|
+
"text": "AlertCounts"
|
|
49248
|
+
},
|
|
49249
|
+
"default": "{}",
|
|
49250
|
+
"description": "Alert count per severity (`countAlarm`, `countWarning`, `countCaution` and the `level-*` counts); counts of zero or less are not shown."
|
|
49251
|
+
},
|
|
49252
|
+
{
|
|
49253
|
+
"kind": "field",
|
|
49254
|
+
"name": "shelvedCount",
|
|
49255
|
+
"type": {
|
|
49256
|
+
"text": "number"
|
|
49257
|
+
},
|
|
49258
|
+
"default": "0",
|
|
49259
|
+
"description": "Number of shelved alerts, shown as a neutral badge after the severities; hidden at zero."
|
|
49260
|
+
},
|
|
49261
|
+
{
|
|
49262
|
+
"kind": "method",
|
|
49263
|
+
"name": "render"
|
|
49264
|
+
},
|
|
49265
|
+
{
|
|
49266
|
+
"kind": "field",
|
|
49267
|
+
"name": "styles",
|
|
49268
|
+
"static": true
|
|
49269
|
+
}
|
|
49270
|
+
],
|
|
49271
|
+
"superclass": {
|
|
49272
|
+
"name": "LitElement",
|
|
49273
|
+
"package": "lit"
|
|
49274
|
+
},
|
|
49275
|
+
"customElement": true
|
|
49276
|
+
}
|
|
49277
|
+
],
|
|
49278
|
+
"exports": [
|
|
49279
|
+
{
|
|
49280
|
+
"kind": "js",
|
|
49281
|
+
"name": "ObcAlertCounterItem",
|
|
49282
|
+
"declaration": {
|
|
49283
|
+
"name": "ObcAlertCounterItem",
|
|
49284
|
+
"module": "src/components/alert-counter-item/alert-counter-item.ts"
|
|
49285
|
+
}
|
|
49286
|
+
}
|
|
49287
|
+
]
|
|
49288
|
+
},
|
|
48967
49289
|
{
|
|
48968
49290
|
"kind": "javascript-module",
|
|
48969
49291
|
"path": "src/components/alert-floating-item/alert-floating-item.ts",
|
|
@@ -49105,7 +49427,7 @@
|
|
|
49105
49427
|
"declarations": [
|
|
49106
49428
|
{
|
|
49107
49429
|
"kind": "class",
|
|
49108
|
-
"description": "`<obc-alert-frame>` – A bordered frame component for visually emphasizing alert or status messages.\n\nThe alert frame provides a prominent outline and optional \"flap\" with status icon to draw attention to critical information or UI regions. It supports multiple visual variants and thicknesses, and can be configured to indicate different alert statuses (alarm, warning, caution). The component is designed to wrap content and visually distinguish it from the surrounding UI.\n\n### Features\n- **Variants (type):**\n - `regular`: Standard outlined frame with no flap or icon.\n - `small-side-flip`: Adds a small side flap with a status icon at the top right.\n - `large-side-flip`: Adds a larger, vertical side flap with a status icon and optional custom icon.\n - `bottom-flip`: Adds a bottom flap with a status icon, label, and timer slots.\n- **Thickness options:** Choose between `small` (thin border) and `large` (thick border) for visual emphasis.\n- **Status indication:** Displays different color schemes and icons for the legacy statuses (`alarm`, `warning`, `caution`) and the level statuses (`level-critical`, `level-high`, `level-medium`, `level-low`, `level-diagnostic`).\n- **Acknowledgement mode:** The `mode` property reflects the alert lifecycle state — `acked-active` (default, steady), `unacked-active` (flashes) and `unacked-rectified` (dashed, flashes) — and `flashingSpeed` picks the tempo.\n- **Content wrapping:** When `wrapContent` is true, the frame wraps and sizes itself to its slotted content rather than overlaying a fixed region.\n- **Customizable corners:** Each corner can be set to a sharp (non-rounded) edge for integration with other UI elements.\n- **Slot-based content:** Supports custom icons, labels, and timers in flap variants via named slots.\n\n### Usage Guidelines\nUse `obc-alert-frame` to highlight important content, such as alerts, warnings, or status panels. The flap variants are ideal for drawing extra attention to urgent or time-sensitive information, while the regular type provides a subtle but clear border. Choose the status to match the severity of the message (alarm, warning, caution). Adjust thickness for visual hierarchy—use large for high-priority alerts and small for less critical notices.\n\n**TODO(designer):** Provide guidance on when to use each flap type (small-side-flip, large-side-flip, bottom-flip) and recommended scenarios for sharp edge usage.\n\n### Features/Variants\n- **Type (Visual Variant):**\n - `regular`: Simple outlined frame, no flap or icon.\n - `small-side-flip`: Small right-side flap with status icon.\n - `large-side-flip`: Large vertical right-side flap with status icon and optional custom icon.\n - `bottom-flip`: Bottom flap with status icon, label, and timer.\n- **Thickness:** `small` (default) or `large` for border width.\n- **Status:** `alarm`, `warning`, `caution`, or the level severities (`level-critical`, `level-high`, `level-medium`, `level-low`, `level-diagnostic`)—affects color and icon.\n- **Mode:** `acked-active` (default), `unacked-active`, or `unacked-rectified`—affects blinking/animation.\n- **Corner Customization:** Each corner can be made sharp (not rounded) via boolean properties.\n\n### Slots and Content Structure\n\n| Slot Name | Renders When... | Purpose |\n|-----------|-----------------|---------|\n| (default) | Always | Main content inside the alert frame. |\n| icon | `large-side-flip` or `bottom-flip` | Custom icon displayed in the flap (in addition to status icon). |\n| label | `bottom-flip` only | Label text shown in the bottom flap. |\n| timer | `bottom-flip` only | Timer or time label in the bottom flap. |\n\n### Properties and Attributes\n- `type`: Selects the visual variant/flap style. Default is `small-side-flip`.\n- `thickness`: Controls border thickness (`small` or `large`). Default is `small`.\n- `status`: Sets the alert status and color/icon (`alarm`, `warning`, `caution`, or the `level-*` severities). Default is `alarm`.\n- `mode`: Acknowledgement lifecycle state (`acked-active`, `unacked-active`, `unacked-rectified`) controlling blinking/animation. Default is `acked-active`.\n- `wrapContent`: When true, the frame wraps and sizes to its slotted content instead of overlaying a fixed region. Default is `false`.\n- `sharpEdgeTopLeft`, `sharpEdgeTopRight`, `sharpEdgeBottomLeft`, `sharpEdgeBottomRight`: Boolean flags to make each corner sharp instead of rounded.\n\n### Best Practices and Constraints\n- Use the status property to match the severity of the alert.\n- Only use the bottom-flip variant when both label and timer are relevant.\n- For visual consistency, align sharp edge settings with adjacent UI elements.\n- The default slot is for the main content; use named slots for icons, labels, and timers as needed.\n\n### Example:\n```\n<obc-alert-frame\n type=\"bottom-flip\"\n thickness=\"large\"\n status=\"warning\"\n sharpEdgeTopLeft\n>\n <obi-placeholder slot=\"icon\"></obi-placeholder>\n <div slot=\"label\">Low Battery</div>\n <div slot=\"timer\">00:15</div>\n <div>Critical system message goes here.</div>\n</obc-alert-frame>\n```",
|
|
49430
|
+
"description": "`<obc-alert-frame>` – A bordered frame component for visually emphasizing alert or status messages.\n\nThe alert frame provides a prominent outline and optional \"flap\" with status icon to draw attention to critical information or UI regions. It supports multiple visual variants and thicknesses, and can be configured to indicate different alert statuses (alarm, warning, caution). The component is designed to wrap content and visually distinguish it from the surrounding UI.\n\n### Features\n- **Variants (type):**\n - `regular`: Standard outlined frame with no flap or icon.\n - `small-side-flip`: Adds a small side flap with a status icon at the top right.\n - `large-side-flip`: Adds a larger, vertical side flap with a status icon and optional custom icon.\n - `bottom-flip`: Adds a bottom flap with a status icon, label, and timer slots.\n- **Thickness options:** Choose between `small` (thin border) and `large` (thick border) for visual emphasis.\n- **Centred stroke:** The stroke straddles the frame edge, half over the content and half outside, so frames on touching components share one edge. While flashing it grows by 1 px on each side.\n- **Status indication:** Displays different color schemes and icons for the legacy statuses (`alarm`, `warning`, `caution`) and the level statuses (`level-critical`, `level-high`, `level-medium`, `level-low`, `level-diagnostic`).\n- **Acknowledgement mode:** The `mode` property reflects the alert lifecycle state — `acked-active` (default, steady), `unacked-active` (flashes) and `unacked-rectified` (dashed, flashes) — and `flashingSpeed` picks the tempo.\n- **Content wrapping:** When `wrapContent` is true, the frame wraps and sizes itself to its slotted content rather than overlaying a fixed region.\n- **Customizable corners:** Each corner can be set to a sharp (non-rounded) edge for integration with other UI elements.\n- **Slot-based content:** Supports custom icons, labels, and timers in flap variants via named slots.\n\n### Usage Guidelines\nUse `obc-alert-frame` to highlight important content, such as alerts, warnings, or status panels. The flap variants are ideal for drawing extra attention to urgent or time-sensitive information, while the regular type provides a subtle but clear border. Choose the status to match the severity of the message (alarm, warning, caution). Adjust thickness for visual hierarchy—use large for high-priority alerts and small for less critical notices.\n\n**TODO(designer):** Provide guidance on when to use each flap type (small-side-flip, large-side-flip, bottom-flip) and recommended scenarios for sharp edge usage.\n\n### Features/Variants\n- **Type (Visual Variant):**\n - `regular`: Simple outlined frame, no flap or icon.\n - `small-side-flip`: Small right-side flap with status icon.\n - `large-side-flip`: Large vertical right-side flap with status icon and optional custom icon.\n - `bottom-flip`: Bottom flap with status icon, label, and timer.\n- **Thickness:** `small` (default) or `large` for border width.\n- **Status:** `alarm`, `warning`, `caution`, or the level severities (`level-critical`, `level-high`, `level-medium`, `level-low`, `level-diagnostic`)—affects color and icon.\n- **Mode:** `acked-active` (default), `unacked-active`, or `unacked-rectified`—affects blinking/animation.\n- **Corner Customization:** Each corner can be made sharp (not rounded) via boolean properties.\n\n### Slots and Content Structure\n\n| Slot Name | Renders When... | Purpose |\n|-----------|-----------------|---------|\n| (default) | Always | Main content inside the alert frame. |\n| icon | `large-side-flip` or `bottom-flip` | Custom icon displayed in the flap (in addition to status icon). |\n| label | `bottom-flip` only | Label text shown in the bottom flap. |\n| timer | `bottom-flip` only | Timer or time label in the bottom flap. |\n\n### Properties and Attributes\n- `type`: Selects the visual variant/flap style. Default is `small-side-flip`.\n- `thickness`: Controls border thickness (`small` or `large`). Default is `small`.\n- `status`: Sets the alert status and color/icon (`alarm`, `warning`, `caution`, or the `level-*` severities). Default is `alarm`.\n- `mode`: Acknowledgement lifecycle state (`acked-active`, `unacked-active`, `unacked-rectified`) controlling blinking/animation. Default is `acked-active`.\n- `wrapContent`: When true, the frame wraps and sizes to its slotted content instead of overlaying a fixed region. Default is `false`.\n- `sharpEdgeTopLeft`, `sharpEdgeTopRight`, `sharpEdgeBottomLeft`, `sharpEdgeBottomRight`: Boolean flags to make each corner sharp instead of rounded.\n\n### Best Practices and Constraints\n- Use the status property to match the severity of the alert.\n- Only use the bottom-flip variant when both label and timer are relevant.\n- For visual consistency, align sharp edge settings with adjacent UI elements.\n- The default slot is for the main content; use named slots for icons, labels, and timers as needed.\n\n### Example:\n```\n<obc-alert-frame\n type=\"bottom-flip\"\n thickness=\"large\"\n status=\"warning\"\n sharpEdgeTopLeft\n>\n <obi-placeholder slot=\"icon\"></obi-placeholder>\n <div slot=\"label\">Low Battery</div>\n <div slot=\"timer\">00:15</div>\n <div>Critical system message goes here.</div>\n</obc-alert-frame>\n```",
|
|
49109
49431
|
"name": "ObcAlertFrame",
|
|
49110
49432
|
"slots": [
|
|
49111
49433
|
{
|
|
@@ -49172,7 +49494,7 @@
|
|
|
49172
49494
|
"type": {
|
|
49173
49495
|
"text": "ObcAlertFrameFlashEffect"
|
|
49174
49496
|
},
|
|
49175
|
-
"description": "`outline` (default) grows the outline by 2 px while on in one step; `outline-eased` grows it over a 50 ms transition, a design-evaluation option."
|
|
49497
|
+
"description": "`outline` (default) grows the outline by 2 px, 1 px each side, while on in one step; `outline-eased` grows it over a 50 ms transition, a design-evaluation option."
|
|
49176
49498
|
},
|
|
49177
49499
|
{
|
|
49178
49500
|
"kind": "field",
|
|
@@ -71324,7 +71646,7 @@
|
|
|
71324
71646
|
"type": {
|
|
71325
71647
|
"text": "TreeNavigationItemAlerts | undefined"
|
|
71326
71648
|
},
|
|
71327
|
-
"description": "Per-severity alert counts for the row's trailing badge(s). Omit (or leave\nevery count at 0) for a row with no alerts. See TreeNavigationItemAlerts.\n\n- When `combine` is true, a single badge is shown: its number is the sum\n of all counts and its severity is the highest category present\n
|
|
71649
|
+
"description": "Per-severity alert counts for the row's trailing badge(s). Omit (or leave\nevery count at 0) for a row with no alerts. See TreeNavigationItemAlerts.\n\n- When `combine` is true, a single badge is shown: its number is the sum\n of all counts and its severity is the highest category present, ranked\n by `ALERT_SEVERITY_PRIORITY`.\n- Otherwise one badge is shown per count greater than 0, ordered most to\n least severe and spaced by the alert-counter spacing token."
|
|
71328
71650
|
},
|
|
71329
71651
|
{
|
|
71330
71652
|
"kind": "field",
|
|
@@ -71366,10 +71688,10 @@
|
|
|
71366
71688
|
"kind": "field",
|
|
71367
71689
|
"name": "alertBadges",
|
|
71368
71690
|
"type": {
|
|
71369
|
-
"text": "
|
|
71691
|
+
"text": "RankedAlertCount[]"
|
|
71370
71692
|
},
|
|
71371
71693
|
"privacy": "private",
|
|
71372
|
-
"description": "The badge(s) to render from `alerts`,
|
|
71694
|
+
"description": "The badge(s) to render from `alerts`, ranked by `rankAlertCounts`.",
|
|
71373
71695
|
"readonly": true
|
|
71374
71696
|
},
|
|
71375
71697
|
{
|