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

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 (59) hide show
  1. package/bundle/openbridge-webcomponents.bundle.js +4865 -4498
  2. package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
  3. package/custom-elements.json +259 -40
  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/components/checkbox-item/checkbox-item.css.js +137 -12
  7. package/dist/components/checkbox-item/checkbox-item.css.js.map +1 -1
  8. package/dist/components/checkbox-item/checkbox-item.d.ts +68 -39
  9. package/dist/components/checkbox-item/checkbox-item.d.ts.map +1 -1
  10. package/dist/components/checkbox-item/checkbox-item.js +66 -25
  11. package/dist/components/checkbox-item/checkbox-item.js.map +1 -1
  12. package/dist/components/checkbox-list/checkbox-list-visibility.d.ts +16 -0
  13. package/dist/components/checkbox-list/checkbox-list-visibility.d.ts.map +1 -0
  14. package/dist/components/checkbox-list/checkbox-list-visibility.js +12 -0
  15. package/dist/components/checkbox-list/checkbox-list-visibility.js.map +1 -0
  16. package/dist/components/checkbox-list/checkbox-list.css.js +23 -0
  17. package/dist/components/checkbox-list/checkbox-list.css.js.map +1 -0
  18. package/dist/components/checkbox-list/checkbox-list.d.ts +66 -0
  19. package/dist/components/checkbox-list/checkbox-list.d.ts.map +1 -0
  20. package/dist/components/checkbox-list/checkbox-list.js +81 -0
  21. package/dist/components/checkbox-list/checkbox-list.js.map +1 -0
  22. package/dist/integration-systems/integration-app-bar/integration-app-bar.css.js +12 -1
  23. package/dist/integration-systems/integration-app-bar/integration-app-bar.css.js.map +1 -1
  24. package/dist/integration-systems/integration-app-bar/integration-app-bar.d.ts +2 -2
  25. package/dist/integration-systems/integration-app-bar/integration-app-bar.d.ts.map +1 -1
  26. package/dist/integration-systems/integration-app-bar/integration-app-bar.js +1 -21
  27. package/dist/integration-systems/integration-app-bar/integration-app-bar.js.map +1 -1
  28. package/dist/navigation-instruments/readout/readout.d.ts +11 -2
  29. package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
  30. package/dist/navigation-instruments/readout/readout.js +18 -0
  31. package/dist/navigation-instruments/readout/readout.js.map +1 -1
  32. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js +25 -0
  33. package/dist/navigation-instruments/readout-list-item/readout-list-item.css.js.map +1 -1
  34. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts +20 -4
  35. package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
  36. package/dist/navigation-instruments/readout-list-item/readout-list-item.js +18 -6
  37. package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/components/app-button/app-button.css +2 -0
  40. package/src/components/app-button/app-button.stories.ts +26 -0
  41. package/src/components/checkbox-item/checkbox-item.css +63 -17
  42. package/src/components/checkbox-item/checkbox-item.spec.ts +115 -0
  43. package/src/components/checkbox-item/checkbox-item.stories.ts +82 -97
  44. package/src/components/checkbox-item/checkbox-item.ts +118 -64
  45. package/src/components/checkbox-list/checkbox-list-visibility.spec.ts +68 -0
  46. package/src/components/checkbox-list/checkbox-list-visibility.ts +23 -0
  47. package/src/components/checkbox-list/checkbox-list.css +13 -0
  48. package/src/components/checkbox-list/checkbox-list.spec.ts +93 -0
  49. package/src/components/checkbox-list/checkbox-list.stories.ts +161 -0
  50. package/src/components/checkbox-list/checkbox-list.ts +130 -0
  51. package/src/integration-systems/integration-app-bar/integration-app-bar-sizing.stories.ts +130 -0
  52. package/src/integration-systems/integration-app-bar/integration-app-bar.css +11 -1
  53. package/src/integration-systems/integration-app-bar/integration-app-bar.ts +3 -27
  54. package/src/navigation-instruments/readout/readout.stories.ts +57 -4
  55. package/src/navigation-instruments/readout/readout.ts +28 -1
  56. package/src/navigation-instruments/readout-list/readout-list.stories.ts +62 -2
  57. package/src/navigation-instruments/readout-list-item/readout-list-item.css +21 -0
  58. package/src/navigation-instruments/readout-list-item/readout-list-item.stories.ts +56 -3
  59. package/src/navigation-instruments/readout-list-item/readout-list-item.ts +37 -19
@@ -5518,7 +5518,7 @@
5518
5518
  {
5519
5519
  "kind": "variable",
5520
5520
  "name": "ObcCheckboxItem",
5521
- "default": "class extends LitElement { constructor() { super(...arguments); this.status = CheckboxStatus.unchecked; this.state = \"enabled\"; this.disabled = false; this.label = \"\"; this.isNested = false; this.isLevel1 = false; this.isLevel2 = false; this.hoverStyle = \"touch-target\"; this.ariaDescribedBy = \"\"; } toggleStatusFromItem() { const isDisabled = this.disabled || this.state === \"disabled\"; if (this.status === CheckboxStatus.checked) { this.status = CheckboxStatus.unchecked; } else { this.status = CheckboxStatus.checked; } this.dispatchEvent( new CustomEvent(\"change\", { detail: { status: this.status, disabled: isDisabled } }) ); } handleItemClick(event) { if (this.disabled || this.state === \"disabled\") return; const path = event.composedPath(); const checkboxElement = this.checkboxElement; if (checkboxElement && path.includes(checkboxElement)) return; this.toggleStatusFromItem(); } handleCheckboxChange(event) { const checkboxEvent = event; this.status = checkboxEvent.detail.status; this.dispatchEvent( new CustomEvent(\"change\", { detail: checkboxEvent.detail }) ); } focus(options) { this.checkboxElement?.focus(options); } render() { const isDisabled = this.disabled || this.state === \"disabled\"; const hasCheckboxHoverEffects = !(this.hoverStyle === \"touch-target\" && !isDisabled); const shouldRenderNestedSpacer = this.isNested && this.isLevel2; const shouldRenderChevronContainer = this.isNested; const shouldRenderChevronIcon = this.isNested && this.isLevel1; const checkboxAriaLabel = this.label.trim().length > 0 ? this.label : void 0; return html` <div class=${classMap({ \"checkbox-item-container\": true, [`status-${this.status}`]: true, [`hover-style-${this.hoverStyle}`]: true, \"is-nested\": this.isNested, \"is-level-1\": this.isLevel1, \"is-level-2\": this.isLevel2, disabled: isDisabled })} @click=${this.handleItemClick} > ${shouldRenderChevronContainer ? html`<div class=\"chevron-container\" aria-hidden=\"true\"> ${shouldRenderChevronIcon ? html`<obi-chevron-right-google class=\"chevron-icon\" ></obi-chevron-right-google>` : nothing} </div>` : nothing} ${shouldRenderNestedSpacer ? html`<div class=\"nested-spacer\" aria-hidden=\"true\"></div>` : nothing} <div class=\"content-container\"> <obc-checkbox .status=${this.status} .state=${CheckboxState.enabled} .disabled=${isDisabled} .hasHoverEffects=${hasCheckboxHoverEffects} aria-label=${ifDefined(checkboxAriaLabel)} aria-describedby=${ifDefined(this.ariaDescribedBy || void 0)} @change=${this.handleCheckboxChange} ></obc-checkbox> <div class=\"checkbox-label-container\"> <span class=\"checkbox-label\">${this.label}</span> </div> </div> </div> `; } }"
5521
+ "default": "class extends LitElement { constructor() { super(...arguments); this.status = CheckboxStatus.unchecked; this.state = \"enabled\"; this.disabled = false; this.label = \"\"; this.description = \"\"; this.level = 0; this.expandable = false; this.expanded = false; this.hoverStyle = \"touch-target\"; this.ariaDescribedBy = \"\"; } get isDisabled() { return this.disabled || this.state === \"disabled\"; } toggleStatusFromItem() { if (this.status === CheckboxStatus.checked) { this.status = CheckboxStatus.unchecked; } else { this.status = CheckboxStatus.checked; } this.dispatchEvent( new CustomEvent(\"change\", { detail: { status: this.status, disabled: this.isDisabled } }) ); } handleItemClick(event) { if (this.isDisabled) return; const path = event.composedPath(); if (this.checkboxElement && path.includes(this.checkboxElement)) return; if (this.chevronButton && path.includes(this.chevronButton)) return; this.toggleStatusFromItem(); } handleChevronClick(event) { event.stopPropagation(); this.dispatchEvent( new CustomEvent(\"expand-toggle\", { detail: !this.expanded, bubbles: true, composed: true }) ); } handleCheckboxChange(event) { const checkboxEvent = event; this.status = checkboxEvent.detail.status; this.dispatchEvent( new CustomEvent(\"change\", { detail: checkboxEvent.detail }) ); } focus(options) { this.checkboxElement?.focus(options); } renderChevron() { if (!this.expandable) return nothing; return html`<button type=\"button\" class=\"chevron-button\" aria-expanded=${this.expanded ? \"true\" : \"false\"} aria-label=${ifDefined(this.label.trim() || void 0)} ?disabled=${this.isDisabled} @click=${this.handleChevronClick} > <span class=\"chevron-visible\"> ${this.expanded ? html`<obi-chevron-down-google class=\"chevron-icon\" ></obi-chevron-down-google>` : html`<obi-chevron-right-google class=\"chevron-icon\" ></obi-chevron-right-google>`} </span> </button>`; } render() { const isDisabled = this.isDisabled; const hasCheckboxHoverEffects = !(this.hoverStyle === \"touch-target\" && !isDisabled); const depth = Math.max(0, Math.floor(this.level) - 1); const hasChevronSlot = this.level >= 1 || this.expandable; const checkboxAriaLabel = this.label.trim().length > 0 ? this.label : void 0; return html` <div class=${classMap({ \"checkbox-item-container\": true, [`status-${this.status}`]: true, [`hover-style-${this.hoverStyle}`]: true, \"is-nested\": hasChevronSlot, \"has-description\": this.description !== \"\", disabled: isDisabled })} @click=${this.handleItemClick} > ${depth > 0 ? html`<div class=\"nested-spacer\" aria-hidden=\"true\" style=${styleMap({ \"--checkbox-item-depth\": String(depth) })} ></div>` : nothing} ${hasChevronSlot ? html`<div class=\"chevron-container\">${this.renderChevron()}</div>` : nothing} <div class=\"content-container\"> <obc-checkbox .status=${this.status} .state=${CheckboxState.enabled} .disabled=${isDisabled} .hasHoverEffects=${hasCheckboxHoverEffects} aria-label=${ifDefined(checkboxAriaLabel)} aria-describedby=${ifDefined(this.ariaDescribedBy || void 0)} @change=${this.handleCheckboxChange} ></obc-checkbox> <div class=\"checkbox-label-container\"> <span class=\"checkbox-label\">${this.label}</span> ${this.description ? html`<span class=\"checkbox-description\" >${this.description}</span >` : nothing} </div> </div> </div> `; } }"
5522
5522
  }
5523
5523
  ],
5524
5524
  "exports": [
@@ -5548,6 +5548,27 @@
5548
5548
  }
5549
5549
  ]
5550
5550
  },
5551
+ {
5552
+ "kind": "javascript-module",
5553
+ "path": "dist/components/checkbox-list/checkbox-list.js",
5554
+ "declarations": [
5555
+ {
5556
+ "kind": "variable",
5557
+ "name": "ObcCheckboxList",
5558
+ "default": "class extends LitElement { constructor() { super(...arguments); this.hoverStyle = ObcCheckboxItemHoverStyle.touchTarget; this.onExpandToggle = (event) => { const row = event.target; if (!(row instanceof ObcCheckboxItem)) return; row.expanded = event.detail; this.sync(); }; } connectedCallback() { super.connectedCallback(); if (!this.hasAttribute(\"role\")) this.setAttribute(\"role\", \"group\"); this.mutationObserver = new MutationObserver(() => this.sync()); this.mutationObserver.observe(this, { childList: true, attributes: true, attributeFilter: [\"expandable\", \"expanded\", \"level\"], subtree: true }); this.addEventListener(\"expand-toggle\", this.onExpandToggle); } disconnectedCallback() { super.disconnectedCallback(); this.mutationObserver?.disconnect(); this.mutationObserver = void 0; this.removeEventListener(\"expand-toggle\", this.onExpandToggle); } firstUpdated() { this.sync(); } updated(changed) { if (changed.has(\"hoverStyle\")) this.sync(); } rows() { return Array.from(this.children).filter( (el) => el instanceof ObcCheckboxItem ); } sync() { const rows = this.rows(); const hidden = computeHiddenRows(rows); rows.forEach((row, i) => { row.hoverStyle = this.hoverStyle; row.hidden = hidden[i]; }); } render() { return html`<div class=\"list\"> <slot @slotchange=${() => this.sync()}></slot> </div>`; } }"
5559
+ }
5560
+ ],
5561
+ "exports": [
5562
+ {
5563
+ "kind": "js",
5564
+ "name": "ObcCheckboxList",
5565
+ "declaration": {
5566
+ "name": "ObcCheckboxList",
5567
+ "module": "dist/components/checkbox-list/checkbox-list.js"
5568
+ }
5569
+ }
5570
+ ]
5571
+ },
5551
5572
  {
5552
5573
  "kind": "javascript-module",
5553
5574
  "path": "dist/components/checkbox/checkbox.js",
@@ -8463,7 +8484,7 @@
8463
8484
  {
8464
8485
  "kind": "variable",
8465
8486
  "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`; } } }); } }"
8487
+ "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
8488
  }
8468
8489
  ],
8469
8490
  "exports": [
@@ -10074,7 +10095,7 @@
10074
10095
  {
10075
10096
  "kind": "variable",
10076
10097
  "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); } }"
10098
+ "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
10099
  }
10079
10100
  ],
10080
10101
  "exports": [
@@ -10196,7 +10217,7 @@
10196
10217
  {
10197
10218
  "kind": "variable",
10198
10219
  "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); } }"
10220
+ "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
10221
  }
10201
10222
  ],
10202
10223
  "exports": [
@@ -51560,28 +51581,24 @@
51560
51581
  "declarations": [
51561
51582
  {
51562
51583
  "kind": "class",
51563
- "description": "`<obc-checkbox-item>` – A list-item wrapper for `<obc-checkbox>` with label\nand optional nested indentation affordances.\n\n### Overview\nCombines checkbox interaction with a row-like container used in menus and\nhierarchical selection lists. The component proxies checkbox state changes,\nsupports touch-target or visual-target interaction behavior, and can render\nnested structure indicators.\n\n### Features\n- Three checkbox statuses: `unchecked`, `checked`, `mixed`.\n- Item state control: `enabled` or `disabled`.\n- Two hover/focus interaction modes: `touch-target` and `visual-target`.\n- Optional nested layout controls via `isNested`, `isLevel1`, `isLevel2`.\n- Forwards `aria-describedby` to the inner checkbox for assistive context.\n\n### Variants\n- **Nested level 1:** Chevron icon is shown.\n- **Nested level 2:** Chevron slot is reserved and nested spacer is shown.\n\n### Usage Guidelines\n- Use for checkbox rows that require text labels and optional hierarchy.\n- Keep `status` as the source of truth for checked/mixed/unchecked value.\n- Use `state=\"disabled\"` or `disabled` to lock interaction.\n- Prefer `hoverStyle=\"touch-target\"` for standard list behavior.\n\n### Slots / Content\n- No named slots. Label content is provided through the `label` property.\n\n### Events\n- `change` Fired when status changes (from row click or inner checkbox).\n **detail:** `{ status, disabled }`\n\n### Best Practices\n- Provide non-empty `label` for accessible naming.\n- Use `aria-describedby` when additional contextual text exists outside.\n- For deep hierarchies, use `isNested` with `isLevel1` / `isLevel2`\n consistently to preserve spacing and alignment.\n\n### Example\n```html\n<obc-checkbox-item\n status=\"mixed\"\n label=\"Include archived items\"\n hoverStyle=\"touch-target\"\n></obc-checkbox-item>\n```",
51584
+ "description": "`<obc-checkbox-item>` – A list row that pairs `<obc-checkbox>` with a label,\nan optional description, and hierarchy affordances for nested lists.\n\n### Overview\nThe row is the touch target: clicking anywhere on it toggles the checkbox,\nexcept on the chevron button, which only asks to expand or collapse. Depth\nis a number (`level`); the row indents itself so a flat sequence of rows\nreads as a tree. `<obc-checkbox-list>` builds on this to hide the rows\nunder a collapsed parent.\n\n### Features\n- Three statuses: `unchecked`, `checked`, `mixed`.\n- `touch-target` or `visual-target` hover treatment.\n- `level` indentation: level 1 reserves the chevron slot, each further\n level adds a spacer.\n- `expandable` rows show a chevron button that fires `expand-toggle`; the\n host owns `expanded`.\n- Optional `description` line under the label.\n- Forwards `aria-describedby` to the inner checkbox.\n\n### Usage Guidelines\n- Keep `status` as the source of truth; update it from the `change` event.\n- Set `expanded` in response to `expand-toggle` the row never flips it\n itself, so a parent (or `<obc-checkbox-list>`) can veto or persist it.\n- Use `level` 0 for a plain list; start at 1 when any row in the list is\n expandable so chevrons and checkboxes line up.\n- Prefer `hoverStyle=\"touch-target\"` for standard list behaviour.\n\n### Accessibility\nThe checkbox and the chevron are separate controls in the natural tab\norder. The chevron is a native button named by the row's label, with the\nstate in `aria-expanded`, following the WAI-ARIA checkbox and disclosure\nbutton patterns.\n\n### Example\n```html\n<obc-checkbox-item\n level=\"1\"\n expandable\n expanded\n status=\"mixed\"\n label=\"Reports\"\n></obc-checkbox-item>\n<obc-checkbox-item\n level=\"2\"\n status=\"checked\"\n label=\"Monthly\"\n description=\"Sent on the 1st\"\n></obc-checkbox-item>\n```",
51564
51585
  "name": "ObcCheckboxItem",
51565
- "slots": [
51566
- {
51567
- "description": "No named slots.",
51568
- "name": ""
51569
- }
51570
- ],
51571
51586
  "members": [
51572
51587
  {
51573
51588
  "kind": "field",
51574
51589
  "name": "status",
51575
51590
  "type": {
51576
51591
  "text": "CheckboxStatus"
51577
- }
51592
+ },
51593
+ "description": "Checkbox status: `unchecked`, `checked` or `mixed`."
51578
51594
  },
51579
51595
  {
51580
51596
  "kind": "field",
51581
51597
  "name": "state",
51582
51598
  "type": {
51583
51599
  "text": "ObcCheckboxItemState"
51584
- }
51600
+ },
51601
+ "description": "Item state: `enabled` or `disabled`."
51585
51602
  },
51586
51603
  {
51587
51604
  "kind": "field",
@@ -51589,7 +51606,8 @@
51589
51606
  "type": {
51590
51607
  "text": "boolean"
51591
51608
  },
51592
- "default": "false"
51609
+ "default": "false",
51610
+ "description": "Disables the row and the inner checkbox."
51593
51611
  },
51594
51612
  {
51595
51613
  "kind": "field",
@@ -51597,38 +51615,52 @@
51597
51615
  "type": {
51598
51616
  "text": "string"
51599
51617
  },
51600
- "default": "''"
51618
+ "default": "''",
51619
+ "description": "Text label; also the checkbox's accessible name."
51601
51620
  },
51602
51621
  {
51603
51622
  "kind": "field",
51604
- "name": "isNested",
51623
+ "name": "description",
51605
51624
  "type": {
51606
- "text": "boolean"
51625
+ "text": "string"
51607
51626
  },
51608
- "default": "false"
51627
+ "default": "''",
51628
+ "description": "Secondary line under the label; hidden when empty."
51609
51629
  },
51610
51630
  {
51611
51631
  "kind": "field",
51612
- "name": "isLevel1",
51632
+ "name": "level",
51633
+ "type": {
51634
+ "text": "number"
51635
+ },
51636
+ "default": "0",
51637
+ "description": "Depth in a nested list. 0 is a plain row; 1 reserves the chevron slot; each level above 1 adds a spacer."
51638
+ },
51639
+ {
51640
+ "kind": "field",
51641
+ "name": "expandable",
51613
51642
  "type": {
51614
51643
  "text": "boolean"
51615
51644
  },
51616
- "default": "false"
51645
+ "default": "false",
51646
+ "description": "Shows the chevron button that fires `expand-toggle`."
51617
51647
  },
51618
51648
  {
51619
51649
  "kind": "field",
51620
- "name": "isLevel2",
51650
+ "name": "expanded",
51621
51651
  "type": {
51622
51652
  "text": "boolean"
51623
51653
  },
51624
- "default": "false"
51654
+ "default": "false",
51655
+ "description": "Whether the row's children are shown; drives the chevron direction and `aria-expanded`."
51625
51656
  },
51626
51657
  {
51627
51658
  "kind": "field",
51628
51659
  "name": "hoverStyle",
51629
51660
  "type": {
51630
51661
  "text": "ObcCheckboxItemHoverStyle"
51631
- }
51662
+ },
51663
+ "description": "Which box shows hover and focus: the whole row (`touch-target`) or the checkbox (`visual-target`)."
51632
51664
  },
51633
51665
  {
51634
51666
  "kind": "field",
@@ -51636,7 +51668,8 @@
51636
51668
  "type": {
51637
51669
  "text": "string"
51638
51670
  },
51639
- "default": "''"
51671
+ "default": "''",
51672
+ "description": "Forwarded to the inner checkbox as `aria-describedby`."
51640
51673
  },
51641
51674
  {
51642
51675
  "kind": "field",
@@ -51646,6 +51679,23 @@
51646
51679
  },
51647
51680
  "privacy": "private"
51648
51681
  },
51682
+ {
51683
+ "kind": "field",
51684
+ "name": "chevronButton",
51685
+ "type": {
51686
+ "text": "HTMLElement | undefined"
51687
+ },
51688
+ "privacy": "private"
51689
+ },
51690
+ {
51691
+ "kind": "field",
51692
+ "name": "isDisabled",
51693
+ "type": {
51694
+ "text": "boolean"
51695
+ },
51696
+ "privacy": "private",
51697
+ "readonly": true
51698
+ },
51649
51699
  {
51650
51700
  "kind": "method",
51651
51701
  "name": "toggleStatusFromItem",
@@ -51664,6 +51714,19 @@
51664
51714
  }
51665
51715
  ]
51666
51716
  },
51717
+ {
51718
+ "kind": "method",
51719
+ "name": "handleChevronClick",
51720
+ "privacy": "private",
51721
+ "parameters": [
51722
+ {
51723
+ "name": "event",
51724
+ "type": {
51725
+ "text": "MouseEvent"
51726
+ }
51727
+ }
51728
+ ]
51729
+ },
51667
51730
  {
51668
51731
  "kind": "method",
51669
51732
  "name": "handleCheckboxChange",
@@ -51696,6 +51759,11 @@
51696
51759
  }
51697
51760
  ]
51698
51761
  },
51762
+ {
51763
+ "kind": "method",
51764
+ "name": "renderChevron",
51765
+ "privacy": "private"
51766
+ },
51699
51767
  {
51700
51768
  "kind": "method",
51701
51769
  "name": "render"
@@ -51713,6 +51781,13 @@
51713
51781
  "text": "ObcCheckboxChangeEvent"
51714
51782
  },
51715
51783
  "description": "Emitted when status changes."
51784
+ },
51785
+ {
51786
+ "name": "expand-toggle",
51787
+ "type": {
51788
+ "text": "ObcCheckboxItemExpandToggleEvent"
51789
+ },
51790
+ "description": "Emitted when the chevron is activated; detail is the next `expanded` value. Bubbles and is composed."
51716
51791
  }
51717
51792
  ],
51718
51793
  "superclass": {
@@ -51733,6 +51808,116 @@
51733
51808
  }
51734
51809
  ]
51735
51810
  },
51811
+ {
51812
+ "kind": "javascript-module",
51813
+ "path": "src/components/checkbox-list/checkbox-list.ts",
51814
+ "declarations": [
51815
+ {
51816
+ "kind": "class",
51817
+ "description": "`<obc-checkbox-list>` – A vertical list of `<obc-checkbox-item>` rows that\ncollapses the rows under a closed parent.\n\n### Overview\nRows are slotted flat, in document order; the tree is read from each row's\n`level`. When an expandable row is collapsed, the list sets `hidden` on\nevery following row with a greater level until a row of equal or smaller\nlevel. The list also applies its `hoverStyle` to every row so a list reads\nas one surface.\n\n### Features\n- Flat markup, level-driven hierarchy — no nesting of elements required.\n- Handles `expand-toggle`: sets the row's `expanded` and recomputes.\n- Reacts to `expandable`, `expanded` and `level` changes made from outside.\n- `hoverStyle` forwarded to slotted rows, including rows added later.\n\n### Usage Guidelines\n- Start levels at 1 when any row is expandable so chevrons and checkboxes\n align; use level 0 for a plain list.\n- The list owns the `hidden` attribute of its rows; do not bind it yourself.\n- Selection stays per row: listen to each row's `change`.\n\n### Accessibility\nThe host is the `role=\"group\"`; rows are ordinary checkboxes and buttons in\nthe natural tab order and hidden rows leave it. This is a group of\ncheckboxes with disclosure buttons, not a tree widget, so there is no\nroving tabindex. Name the group with `aria-label` or `aria-labelledby` on\nthe host.\n\n### Example\n```html\n<obc-checkbox-list aria-label=\"Report types\">\n <obc-checkbox-item level=\"1\" expandable expanded status=\"mixed\" label=\"Reports\"></obc-checkbox-item>\n <obc-checkbox-item level=\"2\" status=\"checked\" label=\"Monthly\"></obc-checkbox-item>\n <obc-checkbox-item level=\"2\" label=\"Quarterly\"></obc-checkbox-item>\n <obc-checkbox-item level=\"1\" expandable label=\"Archive\"></obc-checkbox-item>\n</obc-checkbox-list>\n```",
51818
+ "name": "ObcCheckboxList",
51819
+ "slots": [
51820
+ {
51821
+ "description": "`obc-checkbox-item` rows in document order; hierarchy comes from each row's `level`.",
51822
+ "name": ""
51823
+ }
51824
+ ],
51825
+ "members": [
51826
+ {
51827
+ "kind": "field",
51828
+ "name": "hoverStyle",
51829
+ "type": {
51830
+ "text": "ObcCheckboxItemHoverStyle"
51831
+ },
51832
+ "description": "Hover treatment applied to every slotted row: `touch-target` or `visual-target`."
51833
+ },
51834
+ {
51835
+ "kind": "field",
51836
+ "name": "mutationObserver",
51837
+ "type": {
51838
+ "text": "MutationObserver | undefined"
51839
+ },
51840
+ "privacy": "private"
51841
+ },
51842
+ {
51843
+ "kind": "method",
51844
+ "name": "firstUpdated",
51845
+ "return": {
51846
+ "type": {
51847
+ "text": "void"
51848
+ }
51849
+ }
51850
+ },
51851
+ {
51852
+ "kind": "method",
51853
+ "name": "updated",
51854
+ "return": {
51855
+ "type": {
51856
+ "text": "void"
51857
+ }
51858
+ },
51859
+ "parameters": [
51860
+ {
51861
+ "name": "changed",
51862
+ "type": {
51863
+ "text": "PropertyValues<this>"
51864
+ }
51865
+ }
51866
+ ]
51867
+ },
51868
+ {
51869
+ "kind": "field",
51870
+ "name": "onExpandToggle",
51871
+ "privacy": "private"
51872
+ },
51873
+ {
51874
+ "kind": "method",
51875
+ "name": "rows",
51876
+ "privacy": "private",
51877
+ "return": {
51878
+ "type": {
51879
+ "text": "ObcCheckboxItem[]"
51880
+ }
51881
+ }
51882
+ },
51883
+ {
51884
+ "kind": "method",
51885
+ "name": "sync",
51886
+ "privacy": "private",
51887
+ "return": {
51888
+ "type": {
51889
+ "text": "void"
51890
+ }
51891
+ }
51892
+ },
51893
+ {
51894
+ "kind": "method",
51895
+ "name": "render"
51896
+ },
51897
+ {
51898
+ "kind": "field",
51899
+ "name": "styles",
51900
+ "static": true
51901
+ }
51902
+ ],
51903
+ "superclass": {
51904
+ "name": "LitElement",
51905
+ "package": "lit"
51906
+ },
51907
+ "customElement": true
51908
+ }
51909
+ ],
51910
+ "exports": [
51911
+ {
51912
+ "kind": "js",
51913
+ "name": "ObcCheckboxList",
51914
+ "declaration": {
51915
+ "name": "ObcCheckboxList",
51916
+ "module": "src/components/checkbox-list/checkbox-list.ts"
51917
+ }
51918
+ }
51919
+ ]
51920
+ },
51736
51921
  {
51737
51922
  "kind": "javascript-module",
51738
51923
  "path": "src/components/checkbox/checkbox.ts",
@@ -70544,9 +70729,16 @@
70544
70729
  "kind": "class",
70545
70730
  "description": "",
70546
70731
  "name": "ObcIntegrationAppBar",
70732
+ "cssProperties": [
70733
+ {
70734
+ "description": "Minimum width of each app-button grid track. Tracks may grow equally to fit wider content.",
70735
+ "name": "--obc-integration-app-bar-app-width",
70736
+ "default": "max-content"
70737
+ }
70738
+ ],
70547
70739
  "slots": [
70548
70740
  {
70549
- "description": "Application buttons; slotted elements are normalized to a uniform width",
70741
+ "description": "Application buttons; every button is laid out at the width of the widest one",
70550
70742
  "name": "apps"
70551
70743
  }
70552
70744
  ],
@@ -70555,19 +70747,6 @@
70555
70747
  "kind": "method",
70556
70748
  "name": "render"
70557
70749
  },
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
70750
  {
70572
70751
  "kind": "field",
70573
70752
  "name": "styles",
@@ -84191,7 +84370,7 @@
84191
84370
  "declarations": [
84192
84371
  {
84193
84372
  "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.",
84373
+ "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
84374
  "name": "ObcReadoutListItem",
84196
84375
  "slots": [
84197
84376
  {
@@ -84488,6 +84667,16 @@
84488
84667
  "privacy": "private",
84489
84668
  "readonly": true
84490
84669
  },
84670
+ {
84671
+ "kind": "field",
84672
+ "name": "hasLeadingSrc",
84673
+ "type": {
84674
+ "text": "boolean"
84675
+ },
84676
+ "privacy": "private",
84677
+ "description": "Both leading-src stackings move the source into the label stack.",
84678
+ "readonly": true
84679
+ },
84491
84680
  {
84492
84681
  "kind": "field",
84493
84682
  "name": "resolvedPriority",
@@ -84497,6 +84686,16 @@
84497
84686
  "privacy": "private",
84498
84687
  "readonly": true
84499
84688
  },
84689
+ {
84690
+ "kind": "field",
84691
+ "name": "hasTrailingUnitBox",
84692
+ "type": {
84693
+ "text": "boolean"
84694
+ },
84695
+ "privacy": "private",
84696
+ "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.",
84697
+ "readonly": true
84698
+ },
84500
84699
  {
84501
84700
  "kind": "field",
84502
84701
  "name": "resolvedFractionDigits",
@@ -85299,7 +85498,7 @@
85299
85498
  "type": {
85300
85499
  "text": "ReadoutSize | undefined"
85301
85500
  },
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`."
85501
+ "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
85502
  },
85304
85503
  {
85305
85504
  "kind": "field",
@@ -85465,6 +85664,15 @@
85465
85664
  "privacy": "private",
85466
85665
  "default": "false"
85467
85666
  },
85667
+ {
85668
+ "kind": "field",
85669
+ "name": "hasWarnedHorizontalSize",
85670
+ "type": {
85671
+ "text": "boolean"
85672
+ },
85673
+ "privacy": "private",
85674
+ "default": "false"
85675
+ },
85468
85676
  {
85469
85677
  "kind": "field",
85470
85678
  "name": "sourcePickerContentVisible",
@@ -86174,6 +86382,17 @@
86174
86382
  }
86175
86383
  ]
86176
86384
  },
86385
+ {
86386
+ "kind": "method",
86387
+ "name": "warnHorizontalSizeIgnored",
86388
+ "privacy": "private",
86389
+ "return": {
86390
+ "type": {
86391
+ "text": "void"
86392
+ }
86393
+ },
86394
+ "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."
86395
+ },
86177
86396
  {
86178
86397
  "kind": "method",
86179
86398
  "name": "updated",