@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.143 → 2.0.0-next.144

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +129 -28
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +53 -19
  4. package/dist/components/app-button/app-button.css.js +56 -0
  5. package/dist/components/app-button/app-button.css.js.map +1 -1
  6. package/dist/integration-systems/integration-app-bar/integration-app-bar.css.js +12 -1
  7. package/dist/integration-systems/integration-app-bar/integration-app-bar.css.js.map +1 -1
  8. package/dist/integration-systems/integration-app-bar/integration-app-bar.d.ts +2 -2
  9. package/dist/integration-systems/integration-app-bar/integration-app-bar.d.ts.map +1 -1
  10. package/dist/integration-systems/integration-app-bar/integration-app-bar.js +1 -21
  11. package/dist/integration-systems/integration-app-bar/integration-app-bar.js.map +1 -1
  12. package/dist/navigation-instruments/readout/readout.d.ts +11 -2
  13. package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
  14. package/dist/navigation-instruments/readout/readout.js +18 -0
  15. package/dist/navigation-instruments/readout/readout.js.map +1 -1
  16. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js +25 -0
  17. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js.map +1 -1
  18. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts +20 -4
  19. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
  20. package/dist/navigation-instruments/readout-list-item/readout-list-item.js +18 -6
  21. package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
  22. package/package.json +1 -1
  23. package/src/components/app-button/app-button.css +2 -0
  24. package/src/components/app-button/app-button.stories.ts +26 -0
  25. package/src/integration-systems/integration-app-bar/integration-app-bar-sizing.stories.ts +130 -0
  26. package/src/integration-systems/integration-app-bar/integration-app-bar.css +11 -1
  27. package/src/integration-systems/integration-app-bar/integration-app-bar.ts +3 -27
  28. package/src/navigation-instruments/readout/readout.stories.ts +57 -4
  29. package/src/navigation-instruments/readout/readout.ts +28 -1
  30. package/src/navigation-instruments/readout-list/readout-list.stories.ts +62 -2
  31. package/src/navigation-instruments/readout-list-item/readout-list-item.css +21 -0
  32. package/src/navigation-instruments/readout-list-item/readout-list-item.stories.ts +56 -3
  33. package/src/navigation-instruments/readout-list-item/readout-list-item.ts +37 -19
@@ -8463,7 +8463,7 @@
8463
8463
  {
8464
8464
  "kind": "variable",
8465
8465
  "name": "ObcIntegrationAppBar",
8466
- "default": "class extends LitElement { render() { return html` <div class=\"wrapper\"> <div class=\"left-side\"></div> <div class=\"center\"> <slot name=\"apps\" @slotchange=${this.handleAppsSlotChange}></slot> </div> <div class=\"right-side\"></div> </div> `; } handleAppsSlotChange(event) { const slot = event.target; const buttons = slot.assignedElements(); if (buttons.length === 0) { return; } for (const btn of buttons) { btn.style.width = \"\"; } requestAnimationFrame(() => { const maxWidth = Math.max( ...buttons.map((btn) => btn.getBoundingClientRect().width) ); if (maxWidth > 0) { for (const btn of buttons) { btn.style.width = `${maxWidth}px`; } } }); } }"
8466
+ "default": "class extends LitElement { render() { return html` <div class=\"wrapper\"> <div class=\"left-side\"></div> <div class=\"center\"> <slot name=\"apps\"></slot> </div> <div class=\"right-side\"></div> </div> `; } }"
8467
8467
  }
8468
8468
  ],
8469
8469
  "exports": [
@@ -10074,7 +10074,7 @@
10074
10074
  {
10075
10075
  "kind": "variable",
10076
10076
  "name": "ObcReadoutListItem",
10077
- "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\"; } get resolvedPriority() { return this.priority ?? \"regular\"; } 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} .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; } const hasTrailingUnit = Boolean(this.unit) && this.resolvedStacking !== \"leading-unit\"; if ((this.hasDegree ?? false) && !this.off) { return this.renderDegreeGlyph(this.valueSize, { enhanced: this.rowEnhanced }); } if (hasTrailingUnit) { 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, 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, 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, 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} .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 = stacking === \"leading-src\" && 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.resolvedStacking === \"leading-unit\" || !this.unit) { 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 — byte-for- * byte the pre-6.1 output; 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.resolvedStacking === \"leading-src\" || !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); } }"
10077
+ "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} .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, 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, 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, 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} .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 — byte-for- * byte the pre-6.1 output; 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); } }"
10078
10078
  }
10079
10079
  ],
10080
10080
  "exports": [
@@ -10196,7 +10196,7 @@
10196
10196
  {
10197
10197
  "kind": "variable",
10198
10198
  "name": "ObcReadout",
10199
- "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.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). * The `s` default was confirmed by the design team (2026-08-17). * 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} .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 degree glyph renders on the actual value only (Figma 6.1 review, // 2026-08-18) — the unit is written once for the whole readout, and the // degree follows the same rule; the setpoint / advice implicitly share it. // `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, 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, 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, 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} .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 — the pre-6.1 output; 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, 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, 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); } 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); } }"
10199
+ "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). * The `s` default was confirmed by the design team (2026-08-17). * 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} .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 degree glyph renders on the actual value only (Figma 6.1 review, // 2026-08-18) — the unit is written once for the whole readout, and the // degree follows the same rule; the setpoint / advice implicitly share it. // `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, 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, 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, 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} .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 — the pre-6.1 output; 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, 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, 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); } }"
10200
10200
  }
10201
10201
  ],
10202
10202
  "exports": [
@@ -70544,9 +70544,16 @@
70544
70544
  "kind": "class",
70545
70545
  "description": "",
70546
70546
  "name": "ObcIntegrationAppBar",
70547
+ "cssProperties": [
70548
+ {
70549
+ "description": "Minimum width of each app-button grid track. Tracks may grow equally to fit wider content.",
70550
+ "name": "--obc-integration-app-bar-app-width",
70551
+ "default": "max-content"
70552
+ }
70553
+ ],
70547
70554
  "slots": [
70548
70555
  {
70549
- "description": "Application buttons; slotted elements are normalized to a uniform width",
70556
+ "description": "Application buttons; every button is laid out at the width of the widest one",
70550
70557
  "name": "apps"
70551
70558
  }
70552
70559
  ],
@@ -70555,19 +70562,6 @@
70555
70562
  "kind": "method",
70556
70563
  "name": "render"
70557
70564
  },
70558
- {
70559
- "kind": "method",
70560
- "name": "handleAppsSlotChange",
70561
- "privacy": "private",
70562
- "parameters": [
70563
- {
70564
- "name": "event",
70565
- "type": {
70566
- "text": "Event"
70567
- }
70568
- }
70569
- ]
70570
- },
70571
70565
  {
70572
70566
  "kind": "field",
70573
70567
  "name": "styles",
@@ -84191,7 +84185,7 @@
84191
84185
  "declarations": [
84192
84186
  {
84193
84187
  "kind": "class",
84194
- "description": "`<obc-readout-list-item>` – A compact, dense readout row for lists and tables.\n\nRenders a label, an optional source, an optional unit, and up to three\ncap-height \"readout building blocks\" – advice, setpoint, and value – each a\nfixed-width-reservable numeric segment. Dynamic data is passed as top-level\nprimitives (`value`, `setpoint`, `advice`, `label`, `unit`, `src`). Global\nlayout/format is configured via top-level props (`size`, `priority`,\n`stacking`, `hasDegree`, `dataQuality`, `alert`, …) and per-block tweaks via one\nobject per block (`valueOptions`, `setpointOptions`, `adviceOptions`,\n`unitOptions`, `srcOptions`).\n\n### Features\n- **Building blocks:** value, optional setpoint, and optional advice segments,\n each cap-height-aligned and able to reserve a stable width.\n- **Sizes:** `small`, `medium`, `large` density scales.\n- **Stacking:** `trailing-unit`, `leading-unit`, `leading-src` placement.\n- **Priority:** `regular`/`enhanced` colour emphasis; per-value `weight`\n (`regular`/`semibold`/`bold`) is independent of colour.\n- **Setpoint flip-flop:** swaps emphasis between value and setpoint as the\n value reaches the setpoint.\n- **Data quality:** `low-integrity`/`invalid` styling, combinable with `alert`.\n- **Alert frame:** optional `alert` wrapper (caution/warning/alarm/level).\n- **Clickable:** optionally rendered as a focusable button with `squared`,\n `round-corners`, or `round` corners.\n- **Formatting:** shared `fractionDigits`, width reservation via `maxDigits`\n and per-segment `hintedZeros`; a `null` value renders a dash (`-`).\n- **Text values:** `valueType=\"text\"` renders `value` verbatim (e.g.\n `\"Thermo On\"`) instead of formatting it as a number.\n\n### Usage Guidelines\nUse for dense readout rows in lists/tables. Prefer `<obc-readout>` for rich\nmulti-segment instrument layouts, source pickers, or flyout behaviour.",
84188
+ "description": "`<obc-readout-list-item>` – A compact, dense readout row for lists and tables.\n\nRenders a label, an optional source, an optional unit, and up to three\ncap-height \"readout building blocks\" – advice, setpoint, and value – each a\nfixed-width-reservable numeric segment. Dynamic data is passed as top-level\nprimitives (`value`, `setpoint`, `advice`, `label`, `unit`, `src`). Global\nlayout/format is configured via top-level props (`size`, `priority`,\n`stacking`, `hasDegree`, `dataQuality`, `alert`, …) and per-block tweaks via one\nobject per block (`valueOptions`, `setpointOptions`, `adviceOptions`,\n`unitOptions`, `srcOptions`).\n\n### Features\n- **Building blocks:** value, optional setpoint, and optional advice segments,\n each cap-height-aligned and able to reserve a stable width.\n- **Sizes:** `small`, `medium`, `large` density scales.\n- **Stacking:** `trailing-unit`, `leading-unit`, `leading-src`,\n `leading-src-inline` placement.\n- **Priority:** `regular`/`enhanced` colour emphasis; per-value `weight`\n (`regular`/`semibold`/`bold`) is independent of colour.\n- **Setpoint flip-flop:** swaps emphasis between value and setpoint as the\n value reaches the setpoint.\n- **Data quality:** `low-integrity`/`invalid` styling, combinable with `alert`.\n- **Alert frame:** optional `alert` wrapper (caution/warning/alarm/level).\n- **Clickable:** optionally rendered as a focusable button with `squared`,\n `round-corners`, or `round` corners.\n- **Formatting:** shared `fractionDigits`, width reservation via `maxDigits`\n and per-segment `hintedZeros`; a `null` value renders a dash (`-`).\n- **Text values:** `valueType=\"text\"` renders `value` verbatim (e.g.\n `\"Thermo On\"`) instead of formatting it as a number.\n\n### Usage Guidelines\nUse for dense readout rows in lists/tables. Prefer `<obc-readout>` for rich\nmulti-segment instrument layouts, source pickers, or flyout behaviour.",
84195
84189
  "name": "ObcReadoutListItem",
84196
84190
  "slots": [
84197
84191
  {
@@ -84488,6 +84482,16 @@
84488
84482
  "privacy": "private",
84489
84483
  "readonly": true
84490
84484
  },
84485
+ {
84486
+ "kind": "field",
84487
+ "name": "hasLeadingSrc",
84488
+ "type": {
84489
+ "text": "boolean"
84490
+ },
84491
+ "privacy": "private",
84492
+ "description": "Both leading-src stackings move the source into the label stack.",
84493
+ "readonly": true
84494
+ },
84491
84495
  {
84492
84496
  "kind": "field",
84493
84497
  "name": "resolvedPriority",
@@ -84497,6 +84501,16 @@
84497
84501
  "privacy": "private",
84498
84502
  "readonly": true
84499
84503
  },
84504
+ {
84505
+ "kind": "field",
84506
+ "name": "hasTrailingUnitBox",
84507
+ "type": {
84508
+ "text": "boolean"
84509
+ },
84510
+ "privacy": "private",
84511
+ "description": "Whether a unit box follows the value: a unit, or a reserver alone — the\nblank reserved column keeps a unit-less row aligned with its neighbours.",
84512
+ "readonly": true
84513
+ },
84500
84514
  {
84501
84515
  "kind": "field",
84502
84516
  "name": "resolvedFractionDigits",
@@ -85299,7 +85313,7 @@
85299
85313
  "type": {
85300
85314
  "text": "ReadoutSize | undefined"
85301
85315
  },
85302
- "description": "Density tier. Applies to the vertical direction only — the horizontal arrangement exists in the large tier alone (Figma 6.1: it relies on the label+unit stack aligning with the L-size value caps), so `size` is ignored when `direction` is `horizontal`."
85316
+ "description": "Density tier. Applies to the vertical direction only — the horizontal arrangement exists in the large tier alone (Figma 6.1: it relies on the label+unit stack aligning with the L-size value caps), so `size` is ignored when `direction` is `horizontal`; a horizontal readout given any other `size` logs a console warning once per element."
85303
85317
  },
85304
85318
  {
85305
85319
  "kind": "field",
@@ -85465,6 +85479,15 @@
85465
85479
  "privacy": "private",
85466
85480
  "default": "false"
85467
85481
  },
85482
+ {
85483
+ "kind": "field",
85484
+ "name": "hasWarnedHorizontalSize",
85485
+ "type": {
85486
+ "text": "boolean"
85487
+ },
85488
+ "privacy": "private",
85489
+ "default": "false"
85490
+ },
85468
85491
  {
85469
85492
  "kind": "field",
85470
85493
  "name": "sourcePickerContentVisible",
@@ -86174,6 +86197,17 @@
86174
86197
  }
86175
86198
  ]
86176
86199
  },
86200
+ {
86201
+ "kind": "method",
86202
+ "name": "warnHorizontalSizeIgnored",
86203
+ "privacy": "private",
86204
+ "return": {
86205
+ "type": {
86206
+ "text": "void"
86207
+ }
86208
+ },
86209
+ "description": "A horizontal readout discards any `size` but `large` (see `resolvedSize`).\nA silent discard is indistinguishable from a bug to a consumer, so each\noffending element says so once and logs itself for locating (#1182).\nA removed `size` attribute arrives as `null` and counts as unset."
86210
+ },
86177
86211
  {
86178
86212
  "kind": "method",
86179
86213
  "name": "updated",
@@ -208,6 +208,62 @@ const compentStyle = css`* {
208
208
  "ss04" on;
209
209
  }
210
210
 
211
+ .wrapper.integration {
212
+ border-color: var(--integration-flat-enabled-border-color);
213
+ background-color: var(--integration-flat-enabled-background-color);
214
+ border-width: 1px;
215
+ border-style: solid;
216
+ cursor: pointer;
217
+ --base-border-color: var(--integration-flat-enabled-border-color);
218
+ --base-background-color: var(--integration-flat-enabled-background-color);
219
+ }
220
+
221
+ .wrapper.integration:focus {
222
+ outline: none;
223
+ }
224
+
225
+ .wrapper.integration.activated {
226
+ border-color: var(--integration-flat-activated-border-color);
227
+ background-color: var(--integration-flat-activated-background-color);
228
+ --base-border-color: var(--integration-flat-activated-border-color);
229
+ --base-background-color: var(--integration-flat-activated-background-color);
230
+ }
231
+
232
+ @media (hover:hover) {
233
+
234
+ .wrapper.integration:hover {
235
+ border-color: color-mix(in srgb, var(--integration-flat-hover-border-color) calc(var(--obc-can-hover) * 100%), var(--base-border-color));
236
+ background-color: color-mix(in srgb, var(--integration-flat-hover-background-color) calc(var(--obc-can-hover) * 100%), var(--base-background-color));
237
+ }
238
+ }
239
+
240
+ .wrapper.integration:active {
241
+ border-color: var(--integration-flat-pressed-border-color);
242
+ background-color: var(--integration-flat-pressed-background-color);
243
+ }
244
+
245
+ .wrapper.integration:focus-visible {
246
+ outline-color: var(--border-focus-color);
247
+ outline-width: var(--global-size-spacing-border-weight-focusframe);
248
+ outline-style: solid;
249
+ border-color: var(--container-global-color);
250
+ z-index: 1;
251
+ }
252
+
253
+ .wrapper.integration:disabled {
254
+ border-color: var(--integration-flat-disabled-border-color);
255
+ background-color: var(--integration-flat-disabled-background-color);
256
+ cursor: not-allowed;
257
+ color: var(--integration-on-flat-disabled-color) !important;
258
+ }
259
+
260
+ .wrapper.integration.disabled {
261
+ border-color: var(--integration-flat-disabled-border-color);
262
+ background-color: var(--integration-flat-disabled-background-color);
263
+ cursor: not-allowed;
264
+ color: var(--integration-on-flat-disabled-color) !important;
265
+ }
266
+
211
267
  :is(.wrapper.integration .icon-wrapper) {
212
268
  border-color: var(--integration-normal-enabled-border-color);
213
269
  background-color: var(--integration-normal-enabled-background-color);
@@ -1 +1 @@
1
- {"version":3,"file":"app-button.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"app-button.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -30,10 +30,21 @@ const compentStyle = css`* {
30
30
  }
31
31
 
32
32
  .wrapper .center {
33
- display: flex;
33
+ display: grid;
34
+ grid-auto-flow: column;
35
+ grid-auto-columns: minmax(
36
+ var(--obc-integration-app-bar-app-width, max-content),
37
+ 1fr
38
+ );
34
39
  gap: var(--app-components-app-menu-app-button-spacing);
35
40
  justify-content: center;
36
41
  }
42
+
43
+ /* Svelte wrappers add a slot container that must not become a grid item. */
44
+
45
+ slot[name="apps"]::slotted(:not(obc-app-button)) {
46
+ display: contents;
47
+ }
37
48
  `;
38
49
  export {
39
50
  compentStyle as default
@@ -1 +1 @@
1
- {"version":3,"file":"integration-app-bar.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"integration-app-bar.css.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,11 +1,11 @@
1
1
  import { LitElement } from 'lit';
2
2
  /**
3
- * @slot apps - Application buttons; slotted elements are normalized to a uniform width
3
+ * @slot apps - Application buttons; every button is laid out at the width of the widest one
4
+ * @cssprop [--obc-integration-app-bar-app-width=max-content] - Minimum width of each app-button grid track. Tracks may grow equally to fit wider content.
4
5
  * @experimental
5
6
  */
6
7
  export declare class ObcIntegrationAppBar extends LitElement {
7
8
  render(): import('lit-html').TemplateResult<1>;
8
- private handleAppsSlotChange;
9
9
  static styles: import('lit').CSSResult;
10
10
  }
11
11
  declare global {
@@ -1 +1 @@
1
- {"version":3,"file":"integration-app-bar.d.ts","sourceRoot":"","sources":["../../../src/integration-systems/integration-app-bar/integration-app-bar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAkB,MAAM,KAAK,CAAC;AAIhD;;;GAGG;AACH,qBACa,oBAAqB,SAAQ,UAAU;IACzC,MAAM;IAYf,OAAO,CAAC,oBAAoB;IAyB5B,OAAgB,MAAM,0BAA2B;CAClD;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,yBAAyB,EAAE,oBAAoB,CAAC;KACjD;CACF"}
1
+ {"version":3,"file":"integration-app-bar.d.ts","sourceRoot":"","sources":["../../../src/integration-systems/integration-app-bar/integration-app-bar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAkB,MAAM,KAAK,CAAC;AAIhD;;;;GAIG;AACH,qBACa,oBAAqB,SAAQ,UAAU;IACzC,MAAM;IAYf,OAAgB,MAAM,0BAA2B;CAClD;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,yBAAyB,EAAE,oBAAoB,CAAC;KACjD;CACF"}
@@ -15,32 +15,12 @@ let ObcIntegrationAppBar = class extends LitElement {
15
15
  <div class="wrapper">
16
16
  <div class="left-side"></div>
17
17
  <div class="center">
18
- <slot name="apps" @slotchange=${this.handleAppsSlotChange}></slot>
18
+ <slot name="apps"></slot>
19
19
  </div>
20
20
  <div class="right-side"></div>
21
21
  </div>
22
22
  `;
23
23
  }
24
- handleAppsSlotChange(event) {
25
- const slot = event.target;
26
- const buttons = slot.assignedElements();
27
- if (buttons.length === 0) {
28
- return;
29
- }
30
- for (const btn of buttons) {
31
- btn.style.width = "";
32
- }
33
- requestAnimationFrame(() => {
34
- const maxWidth = Math.max(
35
- ...buttons.map((btn) => btn.getBoundingClientRect().width)
36
- );
37
- if (maxWidth > 0) {
38
- for (const btn of buttons) {
39
- btn.style.width = `${maxWidth}px`;
40
- }
41
- }
42
- });
43
- }
44
24
  };
45
25
  ObcIntegrationAppBar.styles = unsafeCSS(compentStyle);
46
26
  ObcIntegrationAppBar = __decorateClass([
@@ -1 +1 @@
1
- {"version":3,"file":"integration-app-bar.js","sources":["../../../src/integration-systems/integration-app-bar/integration-app-bar.ts"],"sourcesContent":["import {LitElement, html, unsafeCSS} from 'lit';\nimport {customElement} from '../../decorator.js';\nimport compentStyle from './integration-app-bar.css?inline';\n\n/**\n * @slot apps - Application buttons; slotted elements are normalized to a uniform width\n * @experimental\n */\n@customElement('obc-integration-app-bar')\nexport class ObcIntegrationAppBar extends LitElement {\n override render() {\n return html`\n <div class=\"wrapper\">\n <div class=\"left-side\"></div>\n <div class=\"center\">\n <slot name=\"apps\" @slotchange=${this.handleAppsSlotChange}></slot>\n </div>\n <div class=\"right-side\"></div>\n </div>\n `;\n }\n\n private handleAppsSlotChange(event: Event) {\n const slot = event.target as HTMLSlotElement;\n const buttons = slot.assignedElements() as HTMLElement[];\n\n if (buttons.length === 0) {\n return;\n }\n\n for (const btn of buttons) {\n btn.style.width = '';\n }\n\n requestAnimationFrame(() => {\n const maxWidth = Math.max(\n ...buttons.map((btn) => btn.getBoundingClientRect().width)\n );\n\n if (maxWidth > 0) {\n for (const btn of buttons) {\n btn.style.width = `${maxWidth}px`;\n }\n }\n });\n }\n\n static override styles = unsafeCSS(compentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-integration-app-bar': ObcIntegrationAppBar;\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;;AASO,IAAM,uBAAN,cAAmC,WAAW;AAAA,EAC1C,SAAS;AAChB,WAAO;AAAA;AAAA;AAAA;AAAA,0CAI+B,KAAK,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKjE;AAAA,EAEQ,qBAAqB,OAAc;AACzC,UAAM,OAAO,MAAM;AACnB,UAAM,UAAU,KAAK,iBAAA;AAErB,QAAI,QAAQ,WAAW,GAAG;AACxB;AAAA,IACF;AAEA,eAAW,OAAO,SAAS;AACzB,UAAI,MAAM,QAAQ;AAAA,IACpB;AAEA,0BAAsB,MAAM;AAC1B,YAAM,WAAW,KAAK;AAAA,QACpB,GAAG,QAAQ,IAAI,CAAC,QAAQ,IAAI,sBAAA,EAAwB,KAAK;AAAA,MAAA;AAG3D,UAAI,WAAW,GAAG;AAChB,mBAAW,OAAO,SAAS;AACzB,cAAI,MAAM,QAAQ,GAAG,QAAQ;AAAA,QAC/B;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAGF;AAvCa,qBAsCK,SAAS,UAAU,YAAY;AAtCpC,uBAAN,gBAAA;AAAA,EADN,cAAc,yBAAyB;AAAA,GAC3B,oBAAA;"}
1
+ {"version":3,"file":"integration-app-bar.js","sources":["../../../src/integration-systems/integration-app-bar/integration-app-bar.ts"],"sourcesContent":["import {LitElement, html, unsafeCSS} from 'lit';\nimport {customElement} from '../../decorator.js';\nimport compentStyle from './integration-app-bar.css?inline';\n\n/**\n * @slot apps - Application buttons; every button is laid out at the width of the widest one\n * @cssprop [--obc-integration-app-bar-app-width=max-content] - Minimum width of each app-button grid track. Tracks may grow equally to fit wider content.\n * @experimental\n */\n@customElement('obc-integration-app-bar')\nexport class ObcIntegrationAppBar extends LitElement {\n override render() {\n return html`\n <div class=\"wrapper\">\n <div class=\"left-side\"></div>\n <div class=\"center\">\n <slot name=\"apps\"></slot>\n </div>\n <div class=\"right-side\"></div>\n </div>\n `;\n }\n\n static override styles = unsafeCSS(compentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-integration-app-bar': ObcIntegrationAppBar;\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;;AAUO,IAAM,uBAAN,cAAmC,WAAW;AAAA,EAC1C,SAAS;AAChB,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAST;AAGF;AAda,qBAaK,SAAS,UAAU,YAAY;AAbpC,uBAAN,gBAAA;AAAA,EADN,cAAc,yBAAyB;AAAA,GAC3B,oBAAA;"}
@@ -171,7 +171,8 @@ export interface ReadoutSourceOptions extends ReadoutSrcOptions {
171
171
  * @property size - Density tier. Applies to the vertical direction only — the horizontal
172
172
  * arrangement exists in the large tier alone (Figma 6.1: it relies on the
173
173
  * label+unit stack aligning with the L-size value caps), so `size` is
174
- * ignored when `direction` is `horizontal`.
174
+ * ignored when `direction` is `horizontal`; a horizontal readout given any
175
+ * other `size` logs a console warning once per element.
175
176
  * @availableWhen size direction==vertical
176
177
  * @availableWhen stacking direction==vertical
177
178
  * @availableWhen alignment direction==vertical && stacking==stacked
@@ -250,7 +251,7 @@ export declare class ObcReadout extends LitElement {
250
251
  setpoint?: number;
251
252
  hasAdvice: boolean;
252
253
  advice?: number;
253
- /** Density tier. Applies to the vertical direction only — the horizontal arrangement exists in the large tier alone (Figma 6.1: it relies on the label+unit stack aligning with the L-size value caps), so `size` is ignored when `direction` is `horizontal`. */
254
+ /** Density tier. Applies to the vertical direction only — the horizontal arrangement exists in the large tier alone (Figma 6.1: it relies on the label+unit stack aligning with the L-size value caps), so `size` is ignored when `direction` is `horizontal`; a horizontal readout given any other `size` logs a console warning once per element. */
254
255
  size?: ReadoutSize;
255
256
  priority?: ReadoutPriority;
256
257
  direction?: ReadoutDirection;
@@ -279,6 +280,7 @@ export declare class ObcReadout extends LitElement {
279
280
  private deferredSetpointHidePhase;
280
281
  private deferredSetpointHideTimer?;
281
282
  private hasCompletedFirstUpdate;
283
+ private hasWarnedHorizontalSize;
282
284
  private sourcePickerContentVisible;
283
285
  private sourcePickerOptions;
284
286
  private sourcePickerSlot?;
@@ -461,6 +463,13 @@ export declare class ObcReadout extends LitElement {
461
463
  */
462
464
  private renderHorizontalLayout;
463
465
  protected willUpdate(changed: Map<string, unknown>): void;
466
+ /**
467
+ * A horizontal readout discards any `size` but `large` (see `resolvedSize`).
468
+ * A silent discard is indistinguishable from a bug to a consumer, so each
469
+ * offending element says so once and logs itself for locating (#1182).
470
+ * A removed `size` attribute arrives as `null` and counts as unset.
471
+ */
472
+ private warnHorizontalSizeIgnored;
464
473
  updated(changed: Map<string, unknown>): void;
465
474
  private clearDeferredSetpointHide;
466
475
  disconnectedCallback(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"readout.d.ts","sourceRoot":"","sources":["../../../src/navigation-instruments/readout/readout.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"readout.d.ts","sourceRoot":"","sources":["../../../src/navigation-instruments/readout/readout.ts"],"names":[],"mappings":"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"}