@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.115 → 2.0.0-next.116

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.
@@ -1 +1 @@
1
- {"version":3,"file":"readout-block.d.ts","sourceRoot":"","sources":["../../../src/building-blocks/readout-block/readout-block.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAgD,MAAM,KAAK,CAAC;AAK9E,OAAO,qCAAqC,CAAC;AAC7C,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,iCAAiC,CAAC;AACzC,OAAO,yCAAyC,CAAC;AACjD,OAAO,EAML,gBAAgB,EAEjB,MAAM,4DAA4D,CAAC;AACpE,OAAO,EACL,KAAK,gBAAgB,EAEtB,MAAM,6CAA6C,CAAC;AAGrD,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAC,gBAAgB,EAAC,MAAM,4DAA4D,CAAC;AAE5F;;;;;;GAMG;AACH,oBAAY,mBAAmB;IAC7B,KAAK,UAAU;IACf,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAED;;;;;GAKG;AACH,oBAAY,gBAAgB;IAC1B,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,KAAK,UAAU;CAChB;AAED;;;;GAIG;AACH,oBAAY,uBAAuB;IACjC,YAAY,kBAAkB;IAC9B,OAAO,YAAY;CACpB;AAED;;;GAGG;AACH,oBAAY,qBAAqB;IAC/B,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,MAAM,WAAW;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBACa,eAAgB,SAAQ,UAAU;IAC7C,oDAAoD;IAC1B,OAAO,EAAE,mBAAmB,CAC1B;IAE5B;;;OAGG;IACuB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAQ;IAE/D;;;;OAIG;IACuB,SAAS,EAAE,gBAAgB,CAC3B;IAE1B,kDAAkD;IACxB,IAAI,EAAE,gBAAgB,CAA0B;IAE1E;;;;OAIG;IACuB,SAAS,CAAC,EAAE,cAAc,CAAC;IAErD,uCAAuC;IACZ,QAAQ,UAAS;IAE5C,6EAA6E;IACnD,MAAM,EAAE,oBAAoB,CACvB;IAE/B,yDAAyD;IAC9B,SAAS,UAAS;IAE7C,8EAA8E;IACnD,OAAO,UAAS;IAE3C;;;OAGG;IACuB,cAAc,SAAK;IAE7C;;;OAGG;IACuB,SAAS,SAAK;IAExC;;;OAGG;IACwB,WAAW,UAAS;IAE/C,sEAAsE;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAC;IAEjD,0EAA0E;IAC/C,GAAG,UAAS;IAEvC,qCAAqC;IACX,OAAO,SAAS;IAE1C,8DAA8D;IACpC,SAAS,EAAE,mBAAmB,CAC5B;IAE5B,oDAAoD;IAC1B,WAAW,CAAC,EAAE,uBAAuB,CAAC;IAKhE,kEAAkE;IACxC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAAS;IAEpE,4EAA4E;IACjD,QAAQ,UAAS;IAE5C,0EAA0E;IAChD,SAAS,EAAE,qBAAqB,CAC7B;IAEpB,OAAO,CAAC,eAAe,CAAS;IAEzC,OAAO,KAAK,iBAAiB,GAY5B;IAED,OAAO,KAAK,oBAAoB,GAM/B;IAED,2EAA2E;IAC3E,OAAO,KAAK,YAAY,GASvB;IAED;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,gBAAgB,CAMtB;IAEF,OAAO,CAAC,UAAU;IAsBlB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;cAiBN,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAYzD,MAAM;IA+Ef,OAAgB,MAAM,0BAA6B;CACpD;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,mBAAmB,EAAE,eAAe,CAAC;KACtC;CACF"}
1
+ {"version":3,"file":"readout-block.d.ts","sourceRoot":"","sources":["../../../src/building-blocks/readout-block/readout-block.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAgD,MAAM,KAAK,CAAC;AAK9E,OAAO,qCAAqC,CAAC;AAC7C,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,iCAAiC,CAAC;AACzC,OAAO,yCAAyC,CAAC;AACjD,OAAO,EAOL,gBAAgB,EAEjB,MAAM,4DAA4D,CAAC;AACpE,OAAO,EACL,KAAK,gBAAgB,EAEtB,MAAM,6CAA6C,CAAC;AAGrD,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAC,gBAAgB,EAAC,MAAM,4DAA4D,CAAC;AAE5F;;;;;;GAMG;AACH,oBAAY,mBAAmB;IAC7B,KAAK,UAAU;IACf,QAAQ,aAAa;IACrB,MAAM,WAAW;CAClB;AAED;;;;;GAKG;AACH,oBAAY,gBAAgB;IAC1B,KAAK,UAAU;IACf,MAAM,WAAW;IACjB,KAAK,UAAU;CAChB;AAED;;;;GAIG;AACH,oBAAY,uBAAuB;IACjC,YAAY,kBAAkB;IAC9B,OAAO,YAAY;CACpB;AAED;;;GAGG;AACH,oBAAY,qBAAqB;IAC/B,IAAI,SAAS;IACb,MAAM,WAAW;IACjB,MAAM,WAAW;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBACa,eAAgB,SAAQ,UAAU;IAC7C,oDAAoD;IAC1B,OAAO,EAAE,mBAAmB,CAC1B;IAE5B;;;OAGG;IACuB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAQ;IAE/D;;;;OAIG;IACuB,SAAS,EAAE,gBAAgB,CAC3B;IAE1B,kDAAkD;IACxB,IAAI,EAAE,gBAAgB,CAA0B;IAE1E;;;;OAIG;IACuB,SAAS,CAAC,EAAE,cAAc,CAAC;IAErD,uCAAuC;IACZ,QAAQ,UAAS;IAE5C,6EAA6E;IACnD,MAAM,EAAE,oBAAoB,CACvB;IAE/B,yDAAyD;IAC9B,SAAS,UAAS;IAE7C,8EAA8E;IACnD,OAAO,UAAS;IAE3C;;;OAGG;IACuB,cAAc,SAAK;IAE7C;;;OAGG;IACuB,SAAS,SAAK;IAExC;;;OAGG;IACwB,WAAW,UAAS;IAE/C,sEAAsE;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAC;IAEjD,0EAA0E;IAC/C,GAAG,UAAS;IAEvC,qCAAqC;IACX,OAAO,SAAS;IAE1C,8DAA8D;IACpC,SAAS,EAAE,mBAAmB,CAC5B;IAE5B,oDAAoD;IAC1B,WAAW,CAAC,EAAE,uBAAuB,CAAC;IAKhE,kEAAkE;IACxC,KAAK,EAAE,OAAO,GAAG,gBAAgB,CAAS;IAEpE,4EAA4E;IACjD,QAAQ,UAAS;IAE5C,0EAA0E;IAChD,SAAS,EAAE,qBAAqB,CAC7B;IAEpB,OAAO,CAAC,eAAe,CAAS;IAEzC,OAAO,KAAK,iBAAiB,GAY5B;IAED,OAAO,KAAK,oBAAoB,GAS/B;IAED,2EAA2E;IAC3E,OAAO,KAAK,YAAY,GASvB;IAED;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,gBAAgB,CAMtB;IAEF,OAAO,CAAC,UAAU;IAsBlB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;cAiBN,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAYzD,MAAM;IA+Ef,OAAgB,MAAM,0BAA6B;CACpD;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,mBAAmB,EAAE,eAAe,CAAC;KACtC;CACF"}
@@ -6,7 +6,7 @@ import { customElement } from "../../decorator.js";
6
6
  import { ObcTextboxFontWeight, ObcTextboxAlignment, ObcTextboxSize } from "../../components/textbox/textbox.js";
7
7
  import "../../icons/icon-input-right.js";
8
8
  import "../../icons/icon-notification-advice.js";
9
- import { ReadoutValueType, assertReadoutValueType, resolveReadoutNumericValue, resolveReadoutTextValue, formatNumericValue, readoutFormattedInteger } from "../../navigation-instruments/readout/readout-formatters.js";
9
+ import { ReadoutValueType, assertReadoutValueType, resolveReadoutNumericValue, resolveReadoutTextValue, READOUT_UNAVAILABLE_DASH, formatNumericValue, readoutFormattedInteger } from "../../navigation-instruments/readout/readout-formatters.js";
10
10
  import { wrapWithAlertFrame } from "../../components/alert-frame/alert-frame.js";
11
11
  var __defProp = Object.defineProperty;
12
12
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -81,6 +81,9 @@ let ObcReadoutBlock = class extends LitElement {
81
81
  }
82
82
  get numericFormatOptions() {
83
83
  return {
84
+ // The unavailable placeholder stays short (`\u2012.\u2012\u2012`) rather than
85
+ // spelling out every reserved digit position — `maxDigits` already
86
+ // reserves the width, so it simply sits at the right edge of it.
84
87
  showZeroPadding: false,
85
88
  minValueLength: this.maxDigits,
86
89
  fractionDigits: this.fractionDigits
@@ -166,7 +169,7 @@ let ObcReadoutBlock = class extends LitElement {
166
169
  this.valueType
167
170
  );
168
171
  const textValue = resolveReadoutTextValue(this.value, this.valueType);
169
- const text = this.off ? this.offText : isTextMode ? textValue ?? "-" : formatNumericValue(valueForFormat, formatOptions);
172
+ const text = this.off ? this.offText : isTextMode ? textValue ?? READOUT_UNAVAILABLE_DASH : formatNumericValue(valueForFormat, formatOptions);
170
173
  const hintCount = this.off || isTextMode || !this.hintedZeros || valueForFormat === void 0 || valueForFormat < 0 ? 0 : Math.max(this.maxDigits - readoutFormattedInteger(text), 0);
171
174
  const hinted = hintCount > 0 ? "0".repeat(hintCount) : "";
172
175
  const reserver = isTextMode ? this.spaceReserver ?? "" : this.hintedZeros ? this.reserverText : this.widerReserver(this.spaceReserver, this.reserverText);
@@ -1 +1 @@
1
- {"version":3,"file":"readout-block.js","sources":["../../../src/building-blocks/readout-block/readout-block.ts"],"sourcesContent":["import {LitElement, html, nothing, unsafeCSS, type TemplateResult} from 'lit';\nimport {property, state} from 'lit/decorators.js';\nimport {classMap} from 'lit/directives/class-map.js';\nimport componentStyle from './readout-block.css?inline';\nimport {customElement} from '../../decorator.js';\nimport '../../components/textbox/textbox.js';\nimport {\n ObcTextboxSize,\n ObcTextboxFontWeight,\n ObcTextboxAlignment,\n} from '../../components/textbox/textbox.js';\nimport '../../icons/icon-input-right.js';\nimport '../../icons/icon-notification-advice.js';\nimport {\n formatNumericValue,\n readoutFormattedInteger,\n assertReadoutValueType,\n resolveReadoutNumericValue,\n resolveReadoutTextValue,\n ReadoutValueType,\n type ReadoutNumericFormatOptions,\n} from '../../navigation-instruments/readout/readout-formatters.js';\nimport {\n type AlertFrameConfig,\n wrapWithAlertFrame,\n} from '../../components/alert-frame/alert-frame.js';\n\n// Re-exported so consumers can configure typography without a second import path.\nexport {\n ObcTextboxSize,\n ObcTextboxFontWeight,\n ObcTextboxAlignment,\n} from '../../components/textbox/textbox.js';\nexport {ReadoutValueType} from '../../navigation-instruments/readout/readout-formatters.js';\n\n/**\n * Semantic variant of the block. Drives the default marker icon and the\n * colour token; the layout is identical across variants.\n * - `value`: the primary reading (no default marker icon).\n * - `setpoint`: a setpoint reference (default `input-right` marker).\n * - `advice`: an advisory reference (default `notification-advice` marker).\n */\nexport enum ReadoutBlockVariant {\n value = 'value',\n setpoint = 'setpoint',\n advice = 'advice',\n}\n\n/**\n * Density tier of the block — drives icon size, the icon↔number gap, and the\n * degree-column width tier. The number typography size is controlled\n * independently via {@link ObcReadoutBlock.valueSize} so a parent can de-emphasise\n * one block relative to another within the same tier.\n */\nexport enum ReadoutBlockSize {\n small = 'small',\n medium = 'medium',\n large = 'large',\n}\n\n/**\n * Per-block measurement quality, rendered as a non-shifting outline chip.\n * - `low-integrity`: the value is suspect.\n * - `invalid`: the value is invalid.\n */\nexport enum ReadoutBlockDataQuality {\n lowIntegrity = 'low-integrity',\n invalid = 'invalid',\n}\n\n/**\n * Pop-up fade phase for a setpoint block. Driven by the parent's setpoint\n * interaction state machine; only meaningful for `role=\"setpoint\"`.\n */\nexport enum ReadoutBlockHidePhase {\n none = 'none',\n hiding = 'hiding',\n hidden = 'hidden',\n}\n\n/**\n * `<obc-readout-block>` – the most atomic readout primitive: a single\n * cap-height-aligned, width-reservable numeric segment (value / setpoint /\n * advice).\n *\n * It renders one `obc-textbox` number with optional hinted leading zeros, a\n * reservable width, an optional leading marker icon (via the `icon` slot or the\n * role default), an optional trailing degree glyph, an `off`/unavailable text\n * state, per-block data-quality and an optional per-block alert frame.\n *\n * Setting `valueType` to `text` renders `value` verbatim (e.g.\n * `\"Auto\"`) instead of a formatted number.\n *\n * This is a building block used inside `obc-readout-list-item` (and, in a future\n * refactor, inside `obc-readout`); it is not normally used on its own. Colour is\n * inherited from the host context (the parent sets the role colour), so the\n * block stays neutral until placed.\n *\n * @experimental Pilot for the new primitives + per-block options Readout API; the\n * API may change in a future release.\n *\n * @slot icon - Replaces the role's default marker icon.\n *\n * @csspart block - The block container (carries role / tone / data-quality).\n * @csspart block-content - The number + degree group.\n * @csspart block-text - The `obc-textbox` rendering the number.\n * @csspart block-icon - The leading marker-icon container.\n * @csspart degree - The trailing degree-glyph column.\n */\n@customElement('obc-readout-block')\nexport class ObcReadoutBlock extends LitElement {\n /** Semantic variant (value / setpoint / advice). */\n @property({type: String}) variant: ReadoutBlockVariant =\n ReadoutBlockVariant.value;\n\n /**\n * The value; `null`/`undefined` renders a dash. A number by default, or text\n * when {@link valueType} is `text`.\n */\n @property({type: String}) value: number | string | null = null;\n\n /**\n * How {@link value} is interpreted. `number` (default) formats it via\n * `fractionDigits`; `text` renders it verbatim and ignores the numeric\n * format options. Passing text while this is `number` throws.\n */\n @property({type: String}) valueType: ReadoutValueType =\n ReadoutValueType.number;\n\n /** Density tier — icon size, gap, degree tier. */\n @property({type: String}) size: ReadoutBlockSize = ReadoutBlockSize.small;\n\n /**\n * Resolved number-typography size. When unset it is derived from `size`\n * (small→s, medium→m, large→l), so a parent that de-emphasises a block (e.g.\n * a secondary setpoint) can pass a smaller size without changing the tier.\n */\n @property({type: String}) valueSize?: ObcTextboxSize;\n\n /** Accent (in-command) colour tone. */\n @property({type: Boolean}) enhanced = false;\n\n /** Number font weight (regular / semibold / bold); colour is independent. */\n @property({type: String}) weight: ObcTextboxFontWeight =\n ObcTextboxFontWeight.regular;\n\n /** Render the trailing cap-height degree glyph (`°`). */\n @property({type: Boolean}) hasDegree = false;\n\n /** Show the leading marker-icon container (always on for setpoint/advice). */\n @property({type: Boolean}) hasIcon = false;\n\n /**\n * Number of fraction digits.\n * @availableWhen valueType==number\n */\n @property({type: Number}) fractionDigits = 0;\n\n /**\n * Integer digits to reserve / hint (independent of `fractionDigits`).\n * @availableWhen valueType==number\n */\n @property({type: Number}) maxDigits = 0;\n\n /**\n * Render muted leading zeros filling the integer part to `maxDigits`.\n * @availableWhen valueType==number\n */\n @property({type: Boolean}) hintedZeros = false;\n\n /** Explicit longest string to reserve width for (e.g. `\"0000.0\"`). */\n @property({type: String}) spaceReserver?: string;\n\n /** Render `offText` instead of a number (e.g. equipment powered down). */\n @property({type: Boolean}) off = false;\n\n /** Text shown when `off` is true. */\n @property({type: String}) offText = 'OFF';\n\n /** Text alignment of the number within its reserved width. */\n @property({type: String}) alignment: ObcTextboxAlignment =\n ObcTextboxAlignment.Right;\n\n /** Per-block measurement quality (outline chip). */\n @property({type: String}) dataQuality?: ReadoutBlockDataQuality;\n\n // `boolean | …` (not `false | …`): the generated Angular wrapper widens a\n // literal-`false` union to `boolean`. `wrapWithAlertFrame` treats any\n // non-object as \"no frame\", so accepting `boolean` is harmless.\n /** Per-block alert frame; nests inside any parent alert frame. */\n @property({type: Object}) alert: boolean | AlertFrameConfig = false;\n\n /** Setpoint focus (touch) state — only meaningful for `role=\"setpoint\"`. */\n @property({type: Boolean}) touching = false;\n\n /** Setpoint pop-up fade phase — only meaningful for `role=\"setpoint\"`. */\n @property({type: String}) hidePhase: ReadoutBlockHidePhase =\n ReadoutBlockHidePhase.none;\n\n @state() private hasAssignedIcon = false;\n\n private get resolvedValueSize(): ObcTextboxSize {\n if (this.valueSize) {\n return this.valueSize;\n }\n switch (this.size) {\n case ReadoutBlockSize.large:\n return ObcTextboxSize.l;\n case ReadoutBlockSize.medium:\n return ObcTextboxSize.m;\n default:\n return ObcTextboxSize.s;\n }\n }\n\n private get numericFormatOptions(): ReadoutNumericFormatOptions {\n return {\n showZeroPadding: false,\n minValueLength: this.maxDigits,\n fractionDigits: this.fractionDigits,\n };\n }\n\n /** Widest possible value string for width reservation (e.g. `\"000.0\"`). */\n private get reserverText(): string {\n const maxDigits = this.maxDigits;\n if (maxDigits <= 0) {\n return '';\n }\n const integer = '0'.repeat(Math.max(maxDigits, 1));\n return this.fractionDigits > 0\n ? `${integer}.${'0'.repeat(this.fractionDigits)}`\n : integer;\n }\n\n /**\n * Effective width reserver: the wider of the explicit `spaceReserver` and the\n * `maxDigits`/`fractionDigits`-derived reserve, so an explicit reserver can\n * never reserve *less* than the formatted value needs. Under tabular-nums the\n * rendered width is proportional to character count, so \"wider\" compares length.\n */\n private widerReserver(explicit: string | undefined, derived: string): string {\n if (!explicit) {\n return derived;\n }\n if (!derived) {\n return explicit;\n }\n return explicit.length >= derived.length ? explicit : derived;\n }\n\n private dataQualityClasses(): Record<string, boolean> {\n return {\n 'data-low-integrity':\n this.dataQuality === ReadoutBlockDataQuality.lowIntegrity,\n 'data-invalid': this.dataQuality === ReadoutBlockDataQuality.invalid,\n };\n }\n\n private onIconSlotChange = (event: Event): void => {\n // `slotchange` fires after the first render once content is (re)assigned,\n // so it covers the initial state without a firstUpdated re-render.\n this.hasAssignedIcon =\n (event.target as HTMLSlotElement).assignedElements({flatten: true})\n .length > 0;\n };\n\n private renderIcon(): TemplateResult | typeof nothing {\n // Value blocks only show an icon when asked; setpoint/advice always reserve\n // their marker. The role default is rendered as a direct child (so it sizes\n // via `.block-icon obi-*`) and hidden once an icon is assigned to the slot —\n // `{flatten: true}` sees through a forwarded-but-empty parent slot, so the\n // default still shows when the consumer overrides nothing.\n const showIcon = this.variant !== ReadoutBlockVariant.value || this.hasIcon;\n if (!showIcon) {\n return nothing;\n }\n let fallback: TemplateResult | typeof nothing = nothing;\n if (this.variant === ReadoutBlockVariant.setpoint) {\n fallback = html`<obi-input-right></obi-input-right>`;\n } else if (this.variant === ReadoutBlockVariant.advice) {\n fallback = html`<obi-notification-advice></obi-notification-advice>`;\n }\n return html`<span class=\"block-icon\" part=\"block-icon\" aria-hidden=\"true\">\n <slot name=\"icon\" @slotchange=${this.onIconSlotChange}></slot>\n ${this.hasAssignedIcon ? nothing : fallback}\n </span>`;\n }\n\n /**\n * A cap-height `°` column whose width scales with the number size. Inherits the\n * block's colour.\n */\n private renderDegreeGlyph(size: ObcTextboxSize): TemplateResult {\n return html`\n <span\n class=${classMap({\n 'degree-column': true,\n [`degree-${size}`]: true,\n 'degree-inherit': true,\n })}\n part=\"degree\"\n >\n <obc-textbox class=\"degree-glyph\" .size=${size} alignment=\"center\"\n >°</obc-textbox\n >\n </span>\n `;\n }\n\n protected override willUpdate(changed: Map<string, unknown>): void {\n super.willUpdate(changed);\n // Validated on EVERY update, deliberately NOT gated on `value`/`valueType`\n // appearing in `changed`. When this assertion throws, Lit's `performUpdate`\n // catch calls `__markUpdated()`, which clears the changed-properties map. A\n // later update driven by any OTHER property — inside `obc-readout-list`,\n // `align()` writing the shared reservers — would then see no `value` in\n // `changed`, skip the check, and render the invalid value as a plain dash:\n // exactly the silent failure this assertion exists to prevent.\n assertReadoutValueType('obc-readout-block', this.value, this.valueType);\n }\n\n override render() {\n const valueSize = this.resolvedValueSize;\n const formatOptions = this.numericFormatOptions;\n const isTextMode = this.valueType === ReadoutValueType.text;\n const valueForFormat = resolveReadoutNumericValue(\n this.value,\n this.valueType\n );\n const textValue = resolveReadoutTextValue(this.value, this.valueType);\n // Text mode renders verbatim and ignores the numeric format options; a\n // blank / missing text value still falls back to the unavailable dash.\n const text = this.off\n ? this.offText\n : isTextMode\n ? (textValue ?? '-')\n : formatNumericValue(valueForFormat, formatOptions);\n // Hinted zeros pad the INTEGER part up to `maxDigits`, independent of\n // `fractionDigits` (the decimal point and fraction digits never count toward\n // `maxDigits`). Negative / dashed values are not padded. Example: value 1.2,\n // maxDigits 3, fractionDigits 1 → \"001.2\".\n const hintCount =\n this.off ||\n isTextMode ||\n !this.hintedZeros ||\n valueForFormat === undefined ||\n valueForFormat < 0\n ? 0\n : Math.max(this.maxDigits - readoutFormattedInteger(text), 0);\n const hinted = hintCount > 0 ? '0'.repeat(hintCount) : '';\n // Hinted zeros own the width — they already fill to `maxDigits` — so when\n // `hintedZeros` is enabled an explicit `spaceReserver` is ignored (it has\n // higher priority). Otherwise the wider of the explicit reserver and the\n // `maxDigits`-derived reserve wins.\n // Text mode ignores the `maxDigits`-derived numeric reserve — only an\n // explicit `spaceReserver` still applies.\n const reserver = isTextMode\n ? (this.spaceReserver ?? '')\n : this.hintedZeros\n ? this.reserverText\n : this.widerReserver(this.spaceReserver, this.reserverText);\n\n const block = html`\n <div\n class=${classMap({\n block: true,\n [`block-${this.variant}`]: true,\n [`size-${this.size}`]: true,\n 'tone-enhanced': this.enhanced,\n touching: this.touching,\n 'is-hiding': this.hidePhase === ReadoutBlockHidePhase.hiding,\n 'is-hidden': this.hidePhase === ReadoutBlockHidePhase.hidden,\n ...this.dataQualityClasses(),\n })}\n part=\"block block-${this.variant}\"\n >\n ${this.renderIcon()}\n <span class=\"block-content\" part=\"block-content\">\n <obc-textbox\n class=\"block-text\"\n part=\"block-text\"\n .size=${valueSize}\n .fontWeight=${this.weight}\n .alignment=${this.alignment}\n .tabularNums=${true}\n >\n ${hinted\n ? html`<span class=\"hinted-zero\" aria-hidden=\"true\"\n >${hinted}</span\n >`\n : nothing}${text}\n ${reserver ? html`<span slot=\"length\">${reserver}</span>` : nothing}\n </obc-textbox>\n ${this.hasDegree ? this.renderDegreeGlyph(valueSize) : nothing}\n </span>\n </div>\n `;\n return wrapWithAlertFrame(this.alert ?? false, block);\n }\n\n static override styles = unsafeCSS(componentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-readout-block': ObcReadoutBlock;\n }\n}\n"],"names":["ReadoutBlockVariant","ReadoutBlockSize","ReadoutBlockDataQuality","ReadoutBlockHidePhase"],"mappings":"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"}
1
+ {"version":3,"file":"readout-block.js","sources":["../../../src/building-blocks/readout-block/readout-block.ts"],"sourcesContent":["import {LitElement, html, nothing, unsafeCSS, type TemplateResult} from 'lit';\nimport {property, state} from 'lit/decorators.js';\nimport {classMap} from 'lit/directives/class-map.js';\nimport componentStyle from './readout-block.css?inline';\nimport {customElement} from '../../decorator.js';\nimport '../../components/textbox/textbox.js';\nimport {\n ObcTextboxSize,\n ObcTextboxFontWeight,\n ObcTextboxAlignment,\n} from '../../components/textbox/textbox.js';\nimport '../../icons/icon-input-right.js';\nimport '../../icons/icon-notification-advice.js';\nimport {\n formatNumericValue,\n readoutFormattedInteger,\n assertReadoutValueType,\n resolveReadoutNumericValue,\n resolveReadoutTextValue,\n READOUT_UNAVAILABLE_DASH,\n ReadoutValueType,\n type ReadoutNumericFormatOptions,\n} from '../../navigation-instruments/readout/readout-formatters.js';\nimport {\n type AlertFrameConfig,\n wrapWithAlertFrame,\n} from '../../components/alert-frame/alert-frame.js';\n\n// Re-exported so consumers can configure typography without a second import path.\nexport {\n ObcTextboxSize,\n ObcTextboxFontWeight,\n ObcTextboxAlignment,\n} from '../../components/textbox/textbox.js';\nexport {ReadoutValueType} from '../../navigation-instruments/readout/readout-formatters.js';\n\n/**\n * Semantic variant of the block. Drives the default marker icon and the\n * colour token; the layout is identical across variants.\n * - `value`: the primary reading (no default marker icon).\n * - `setpoint`: a setpoint reference (default `input-right` marker).\n * - `advice`: an advisory reference (default `notification-advice` marker).\n */\nexport enum ReadoutBlockVariant {\n value = 'value',\n setpoint = 'setpoint',\n advice = 'advice',\n}\n\n/**\n * Density tier of the block — drives icon size, the icon↔number gap, and the\n * degree-column width tier. The number typography size is controlled\n * independently via {@link ObcReadoutBlock.valueSize} so a parent can de-emphasise\n * one block relative to another within the same tier.\n */\nexport enum ReadoutBlockSize {\n small = 'small',\n medium = 'medium',\n large = 'large',\n}\n\n/**\n * Per-block measurement quality, rendered as a non-shifting outline chip.\n * - `low-integrity`: the value is suspect.\n * - `invalid`: the value is invalid.\n */\nexport enum ReadoutBlockDataQuality {\n lowIntegrity = 'low-integrity',\n invalid = 'invalid',\n}\n\n/**\n * Pop-up fade phase for a setpoint block. Driven by the parent's setpoint\n * interaction state machine; only meaningful for `role=\"setpoint\"`.\n */\nexport enum ReadoutBlockHidePhase {\n none = 'none',\n hiding = 'hiding',\n hidden = 'hidden',\n}\n\n/**\n * `<obc-readout-block>` – the most atomic readout primitive: a single\n * cap-height-aligned, width-reservable numeric segment (value / setpoint /\n * advice).\n *\n * It renders one `obc-textbox` number with optional hinted leading zeros, a\n * reservable width, an optional leading marker icon (via the `icon` slot or the\n * role default), an optional trailing degree glyph, an `off`/unavailable text\n * state, per-block data-quality and an optional per-block alert frame.\n *\n * Setting `valueType` to `text` renders `value` verbatim (e.g.\n * `\"Auto\"`) instead of a formatted number.\n *\n * This is a building block used inside `obc-readout-list-item` (and, in a future\n * refactor, inside `obc-readout`); it is not normally used on its own. Colour is\n * inherited from the host context (the parent sets the role colour), so the\n * block stays neutral until placed.\n *\n * @experimental Pilot for the new primitives + per-block options Readout API; the\n * API may change in a future release.\n *\n * @slot icon - Replaces the role's default marker icon.\n *\n * @csspart block - The block container (carries role / tone / data-quality).\n * @csspart block-content - The number + degree group.\n * @csspart block-text - The `obc-textbox` rendering the number.\n * @csspart block-icon - The leading marker-icon container.\n * @csspart degree - The trailing degree-glyph column.\n */\n@customElement('obc-readout-block')\nexport class ObcReadoutBlock extends LitElement {\n /** Semantic variant (value / setpoint / advice). */\n @property({type: String}) variant: ReadoutBlockVariant =\n ReadoutBlockVariant.value;\n\n /**\n * The value; `null`/`undefined` renders a dash. A number by default, or text\n * when {@link valueType} is `text`.\n */\n @property({type: String}) value: number | string | null = null;\n\n /**\n * How {@link value} is interpreted. `number` (default) formats it via\n * `fractionDigits`; `text` renders it verbatim and ignores the numeric\n * format options. Passing text while this is `number` throws.\n */\n @property({type: String}) valueType: ReadoutValueType =\n ReadoutValueType.number;\n\n /** Density tier — icon size, gap, degree tier. */\n @property({type: String}) size: ReadoutBlockSize = ReadoutBlockSize.small;\n\n /**\n * Resolved number-typography size. When unset it is derived from `size`\n * (small→s, medium→m, large→l), so a parent that de-emphasises a block (e.g.\n * a secondary setpoint) can pass a smaller size without changing the tier.\n */\n @property({type: String}) valueSize?: ObcTextboxSize;\n\n /** Accent (in-command) colour tone. */\n @property({type: Boolean}) enhanced = false;\n\n /** Number font weight (regular / semibold / bold); colour is independent. */\n @property({type: String}) weight: ObcTextboxFontWeight =\n ObcTextboxFontWeight.regular;\n\n /** Render the trailing cap-height degree glyph (`°`). */\n @property({type: Boolean}) hasDegree = false;\n\n /** Show the leading marker-icon container (always on for setpoint/advice). */\n @property({type: Boolean}) hasIcon = false;\n\n /**\n * Number of fraction digits.\n * @availableWhen valueType==number\n */\n @property({type: Number}) fractionDigits = 0;\n\n /**\n * Integer digits to reserve / hint (independent of `fractionDigits`).\n * @availableWhen valueType==number\n */\n @property({type: Number}) maxDigits = 0;\n\n /**\n * Render muted leading zeros filling the integer part to `maxDigits`.\n * @availableWhen valueType==number\n */\n @property({type: Boolean}) hintedZeros = false;\n\n /** Explicit longest string to reserve width for (e.g. `\"0000.0\"`). */\n @property({type: String}) spaceReserver?: string;\n\n /** Render `offText` instead of a number (e.g. equipment powered down). */\n @property({type: Boolean}) off = false;\n\n /** Text shown when `off` is true. */\n @property({type: String}) offText = 'OFF';\n\n /** Text alignment of the number within its reserved width. */\n @property({type: String}) alignment: ObcTextboxAlignment =\n ObcTextboxAlignment.Right;\n\n /** Per-block measurement quality (outline chip). */\n @property({type: String}) dataQuality?: ReadoutBlockDataQuality;\n\n // `boolean | …` (not `false | …`): the generated Angular wrapper widens a\n // literal-`false` union to `boolean`. `wrapWithAlertFrame` treats any\n // non-object as \"no frame\", so accepting `boolean` is harmless.\n /** Per-block alert frame; nests inside any parent alert frame. */\n @property({type: Object}) alert: boolean | AlertFrameConfig = false;\n\n /** Setpoint focus (touch) state — only meaningful for `role=\"setpoint\"`. */\n @property({type: Boolean}) touching = false;\n\n /** Setpoint pop-up fade phase — only meaningful for `role=\"setpoint\"`. */\n @property({type: String}) hidePhase: ReadoutBlockHidePhase =\n ReadoutBlockHidePhase.none;\n\n @state() private hasAssignedIcon = false;\n\n private get resolvedValueSize(): ObcTextboxSize {\n if (this.valueSize) {\n return this.valueSize;\n }\n switch (this.size) {\n case ReadoutBlockSize.large:\n return ObcTextboxSize.l;\n case ReadoutBlockSize.medium:\n return ObcTextboxSize.m;\n default:\n return ObcTextboxSize.s;\n }\n }\n\n private get numericFormatOptions(): ReadoutNumericFormatOptions {\n return {\n // The unavailable placeholder stays short (`\\u2012.\\u2012\\u2012`) rather than\n // spelling out every reserved digit position — `maxDigits` already\n // reserves the width, so it simply sits at the right edge of it.\n showZeroPadding: false,\n minValueLength: this.maxDigits,\n fractionDigits: this.fractionDigits,\n };\n }\n\n /** Widest possible value string for width reservation (e.g. `\"000.0\"`). */\n private get reserverText(): string {\n const maxDigits = this.maxDigits;\n if (maxDigits <= 0) {\n return '';\n }\n const integer = '0'.repeat(Math.max(maxDigits, 1));\n return this.fractionDigits > 0\n ? `${integer}.${'0'.repeat(this.fractionDigits)}`\n : integer;\n }\n\n /**\n * Effective width reserver: the wider of the explicit `spaceReserver` and the\n * `maxDigits`/`fractionDigits`-derived reserve, so an explicit reserver can\n * never reserve *less* than the formatted value needs. Under tabular-nums the\n * rendered width is proportional to character count, so \"wider\" compares length.\n */\n private widerReserver(explicit: string | undefined, derived: string): string {\n if (!explicit) {\n return derived;\n }\n if (!derived) {\n return explicit;\n }\n return explicit.length >= derived.length ? explicit : derived;\n }\n\n private dataQualityClasses(): Record<string, boolean> {\n return {\n 'data-low-integrity':\n this.dataQuality === ReadoutBlockDataQuality.lowIntegrity,\n 'data-invalid': this.dataQuality === ReadoutBlockDataQuality.invalid,\n };\n }\n\n private onIconSlotChange = (event: Event): void => {\n // `slotchange` fires after the first render once content is (re)assigned,\n // so it covers the initial state without a firstUpdated re-render.\n this.hasAssignedIcon =\n (event.target as HTMLSlotElement).assignedElements({flatten: true})\n .length > 0;\n };\n\n private renderIcon(): TemplateResult | typeof nothing {\n // Value blocks only show an icon when asked; setpoint/advice always reserve\n // their marker. The role default is rendered as a direct child (so it sizes\n // via `.block-icon obi-*`) and hidden once an icon is assigned to the slot —\n // `{flatten: true}` sees through a forwarded-but-empty parent slot, so the\n // default still shows when the consumer overrides nothing.\n const showIcon = this.variant !== ReadoutBlockVariant.value || this.hasIcon;\n if (!showIcon) {\n return nothing;\n }\n let fallback: TemplateResult | typeof nothing = nothing;\n if (this.variant === ReadoutBlockVariant.setpoint) {\n fallback = html`<obi-input-right></obi-input-right>`;\n } else if (this.variant === ReadoutBlockVariant.advice) {\n fallback = html`<obi-notification-advice></obi-notification-advice>`;\n }\n return html`<span class=\"block-icon\" part=\"block-icon\" aria-hidden=\"true\">\n <slot name=\"icon\" @slotchange=${this.onIconSlotChange}></slot>\n ${this.hasAssignedIcon ? nothing : fallback}\n </span>`;\n }\n\n /**\n * A cap-height `°` column whose width scales with the number size. Inherits the\n * block's colour.\n */\n private renderDegreeGlyph(size: ObcTextboxSize): TemplateResult {\n return html`\n <span\n class=${classMap({\n 'degree-column': true,\n [`degree-${size}`]: true,\n 'degree-inherit': true,\n })}\n part=\"degree\"\n >\n <obc-textbox class=\"degree-glyph\" .size=${size} alignment=\"center\"\n >°</obc-textbox\n >\n </span>\n `;\n }\n\n protected override willUpdate(changed: Map<string, unknown>): void {\n super.willUpdate(changed);\n // Validated on EVERY update, deliberately NOT gated on `value`/`valueType`\n // appearing in `changed`. When this assertion throws, Lit's `performUpdate`\n // catch calls `__markUpdated()`, which clears the changed-properties map. A\n // later update driven by any OTHER property — inside `obc-readout-list`,\n // `align()` writing the shared reservers — would then see no `value` in\n // `changed`, skip the check, and render the invalid value as a plain dash:\n // exactly the silent failure this assertion exists to prevent.\n assertReadoutValueType('obc-readout-block', this.value, this.valueType);\n }\n\n override render() {\n const valueSize = this.resolvedValueSize;\n const formatOptions = this.numericFormatOptions;\n const isTextMode = this.valueType === ReadoutValueType.text;\n const valueForFormat = resolveReadoutNumericValue(\n this.value,\n this.valueType\n );\n const textValue = resolveReadoutTextValue(this.value, this.valueType);\n // Text mode renders verbatim and ignores the numeric format options; a\n // blank / missing text value still falls back to the unavailable dash.\n const text = this.off\n ? this.offText\n : isTextMode\n ? (textValue ?? READOUT_UNAVAILABLE_DASH)\n : formatNumericValue(valueForFormat, formatOptions);\n // Hinted zeros pad the INTEGER part up to `maxDigits`, independent of\n // `fractionDigits` (the decimal point and fraction digits never count toward\n // `maxDigits`). Negative / dashed values are not padded. Example: value 1.2,\n // maxDigits 3, fractionDigits 1 → \"001.2\".\n const hintCount =\n this.off ||\n isTextMode ||\n !this.hintedZeros ||\n valueForFormat === undefined ||\n valueForFormat < 0\n ? 0\n : Math.max(this.maxDigits - readoutFormattedInteger(text), 0);\n const hinted = hintCount > 0 ? '0'.repeat(hintCount) : '';\n // Hinted zeros own the width — they already fill to `maxDigits` — so when\n // `hintedZeros` is enabled an explicit `spaceReserver` is ignored (it has\n // higher priority). Otherwise the wider of the explicit reserver and the\n // `maxDigits`-derived reserve wins.\n // Text mode ignores the `maxDigits`-derived numeric reserve — only an\n // explicit `spaceReserver` still applies.\n const reserver = isTextMode\n ? (this.spaceReserver ?? '')\n : this.hintedZeros\n ? this.reserverText\n : this.widerReserver(this.spaceReserver, this.reserverText);\n\n const block = html`\n <div\n class=${classMap({\n block: true,\n [`block-${this.variant}`]: true,\n [`size-${this.size}`]: true,\n 'tone-enhanced': this.enhanced,\n touching: this.touching,\n 'is-hiding': this.hidePhase === ReadoutBlockHidePhase.hiding,\n 'is-hidden': this.hidePhase === ReadoutBlockHidePhase.hidden,\n ...this.dataQualityClasses(),\n })}\n part=\"block block-${this.variant}\"\n >\n ${this.renderIcon()}\n <span class=\"block-content\" part=\"block-content\">\n <obc-textbox\n class=\"block-text\"\n part=\"block-text\"\n .size=${valueSize}\n .fontWeight=${this.weight}\n .alignment=${this.alignment}\n .tabularNums=${true}\n >\n ${hinted\n ? html`<span class=\"hinted-zero\" aria-hidden=\"true\"\n >${hinted}</span\n >`\n : nothing}${text}\n ${reserver ? html`<span slot=\"length\">${reserver}</span>` : nothing}\n </obc-textbox>\n ${this.hasDegree ? this.renderDegreeGlyph(valueSize) : nothing}\n </span>\n </div>\n `;\n return wrapWithAlertFrame(this.alert ?? false, block);\n }\n\n static override styles = unsafeCSS(componentStyle);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'obc-readout-block': ObcReadoutBlock;\n }\n}\n"],"names":["ReadoutBlockVariant","ReadoutBlockSize","ReadoutBlockDataQuality","ReadoutBlockHidePhase"],"mappings":"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"}
@@ -30,10 +30,32 @@ export declare function isReadoutValueType(value: unknown): value is ReadoutValu
30
30
  * `Number('')` is `0`, a silently wrong reading.
31
31
  */
32
32
  export declare function assertReadoutValueType(tagName: string, value: number | string | null | undefined, valueType: ReadoutValueType): void;
33
- /** The value as a number, or `undefined` when it is text / unavailable. */
33
+ /**
34
+ * The value as a number, or `undefined` when it is text / unavailable.
35
+ *
36
+ * A non-finite number (`NaN`, `±Infinity`) counts as unavailable and renders the
37
+ * dash, the same as `null`. `NaN` is a runtime data condition — a sensor
38
+ * dropout, a `0/0`, a bad parse — not a programmer error, so it must not throw;
39
+ * and `value.toFixed()` would otherwise render the literal text `"NaN"` /
40
+ * `"Infinity"` in place of a reading. This also makes the number path agree with
41
+ * the string path below, which has always resolved a non-finite string to
42
+ * `undefined` — before this, `<obc-readout value="NaN">` rendered a dash while
43
+ * `.value=${NaN}` rendered `"NaN"`.
44
+ */
34
45
  export declare function resolveReadoutNumericValue(value: number | string | null | undefined, valueType: ReadoutValueType): number | undefined;
35
46
  /** The value as display text, or `undefined` when not in text mode / blank. */
36
47
  export declare function resolveReadoutTextValue(value: number | string | null | undefined, valueType: ReadoutValueType): string | undefined;
48
+ /**
49
+ * The character used for an unavailable ("no reading") value.
50
+ *
51
+ * U+2012 FIGURE DASH, not the ASCII hyphen-minus: it is defined to be the same
52
+ * width as a digit, so the placeholder lines up with the reading it stands in
53
+ * for. Measured in Noto Sans with tabular figures at `size="m"` — digit 13.02px,
54
+ * U+2012 13.02px, U+002D 7.02px, en dash 11.02px, em dash 22.02px. With a
55
+ * hyphen, `-.--` sat 46% narrow per character and its decimal point missed the
56
+ * reading's; with U+2012 the point and every fraction position align exactly.
57
+ */
58
+ export declare const READOUT_UNAVAILABLE_DASH = "\u2012";
37
59
  export declare function formatNumericValue(value: number | undefined, options: ReadoutNumericFormatOptions): string;
38
60
  export declare function readoutFormattedInteger(valueText: string): number;
39
61
  export declare function getHintZeros(value: number | undefined, { showZeroPadding, minValueLength, fractionDigits }: ReadoutNumericFormatOptions): string;
@@ -1 +1 @@
1
- {"version":3,"file":"readout-formatters.d.ts","sourceRoot":"","sources":["../../../src/navigation-instruments/readout/readout-formatters.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,2BAA2B,GAAG;IACxC,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;GAIG;AACH,oBAAY,gBAAgB;IAC1B,MAAM,WAAW;IACjB,IAAI,SAAS;CACd;AAID,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAE5E;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,SAAS,EAAE,gBAAgB,GAC1B,IAAI,CAyBN;AAED,2EAA2E;AAC3E,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,SAAS,EAAE,gBAAgB,GAC1B,MAAM,GAAG,SAAS,CAgBpB;AAED,+EAA+E;AAC/E,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,SAAS,EAAE,gBAAgB,GAC1B,MAAM,GAAG,SAAS,CAUpB;AAoBD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,EAAE,2BAA2B,GACnC,MAAM,CAMR;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CASjE;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,EAAC,eAAe,EAAE,cAAc,EAAE,cAAc,EAAC,EAAE,2BAA2B,GAC7E,MAAM,CAeR"}
1
+ {"version":3,"file":"readout-formatters.d.ts","sourceRoot":"","sources":["../../../src/navigation-instruments/readout/readout-formatters.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,2BAA2B,GAAG;IACxC,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;GAIG;AACH,oBAAY,gBAAgB;IAC1B,MAAM,WAAW;IACjB,IAAI,SAAS;CACd;AAID,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAE5E;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,SAAS,EAAE,gBAAgB,GAC1B,IAAI,CAyBN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,SAAS,EAAE,gBAAgB,GAC1B,MAAM,GAAG,SAAS,CAgBpB;AAED,+EAA+E;AAC/E,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,SAAS,EAAE,gBAAgB,GAC1B,MAAM,GAAG,SAAS,CAUpB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,WAAW,CAAC;AA+BjD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,EAAE,2BAA2B,GACnC,MAAM,CAUR;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CASjE;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,EAAC,eAAe,EAAE,cAAc,EAAE,cAAc,EAAC,EAAE,2BAA2B,GAC7E,MAAM,CAeR"}
@@ -32,7 +32,7 @@ function resolveReadoutNumericValue(value, valueType) {
32
32
  return void 0;
33
33
  }
34
34
  if (typeof value === "number") {
35
- return value;
35
+ return Number.isFinite(value) ? value : void 0;
36
36
  }
37
37
  if (isBlank(value)) {
38
38
  return void 0;
@@ -47,6 +47,7 @@ function resolveReadoutTextValue(value, valueType) {
47
47
  const text = typeof value === "number" ? String(value) : value;
48
48
  return isBlank(text) ? void 0 : text;
49
49
  }
50
+ const READOUT_UNAVAILABLE_DASH = "‒";
50
51
  function dashedGenerator({
51
52
  showZeroPadding,
52
53
  minValueLength,
@@ -54,13 +55,13 @@ function dashedGenerator({
54
55
  }) {
55
56
  const visibleDigits = showZeroPadding ? Math.max(minValueLength, 1) : 1;
56
57
  if (fractionDigits < 1) {
57
- return "-".repeat(visibleDigits);
58
+ return READOUT_UNAVAILABLE_DASH.repeat(visibleDigits);
58
59
  }
59
60
  const integerDigits = visibleDigits - fractionDigits;
60
- return "-".repeat(Math.max(integerDigits, 1)) + "." + "-".repeat(fractionDigits);
61
+ return READOUT_UNAVAILABLE_DASH.repeat(Math.max(integerDigits, 1)) + "." + READOUT_UNAVAILABLE_DASH.repeat(fractionDigits);
61
62
  }
62
63
  function formatNumericValue(value, options) {
63
- if (value === void 0) {
64
+ if (value === void 0 || !Number.isFinite(value)) {
64
65
  return dashedGenerator(options);
65
66
  }
66
67
  return value.toFixed(options.fractionDigits);
@@ -89,6 +90,7 @@ function getHintZeros(value, { showZeroPadding, minValueLength, fractionDigits }
89
90
  return "";
90
91
  }
91
92
  export {
93
+ READOUT_UNAVAILABLE_DASH,
92
94
  ReadoutValueType,
93
95
  assertReadoutValueType,
94
96
  formatNumericValue,
@@ -1 +1 @@
1
- {"version":3,"file":"readout-formatters.js","sources":["../../../src/navigation-instruments/readout/readout-formatters.ts"],"sourcesContent":["export type ReadoutNumericFormatOptions = {\n showZeroPadding: boolean;\n minValueLength: number;\n fractionDigits: number;\n};\n\n/**\n * How a readout's `value` is interpreted.\n * - `number`: formatted via `fractionDigits` / `maxDigits`.\n * - `text`: rendered verbatim, with the numeric format options ignored.\n */\nexport enum ReadoutValueType {\n number = 'number',\n text = 'text',\n}\n\nconst READOUT_VALUE_TYPES: readonly string[] = Object.values(ReadoutValueType);\n\n/** Whether `value` is one of the supported {@link ReadoutValueType} values. */\nexport function isReadoutValueType(value: unknown): value is ReadoutValueType {\n return typeof value === 'string' && READOUT_VALUE_TYPES.includes(value);\n}\n\nfunction isBlank(value: string): boolean {\n return value.trim() === '';\n}\n\n/**\n * Throws when `valueType` is not a supported value, or when `value` is text but\n * `valueType` is `number`.\n *\n * `valueType` is validated first because an attribute carries an unchecked\n * string: a typo such as `valuetype=\"strng\"` matches neither mode, so every\n * mode check falls through and the readout silently renders the unavailable\n * dash — the opposite of the loud failure this contract exists to give.\n * `undefined`/`null` are allowed and mean \"use the default\".\n *\n * Attributes are always strings, so a numeric-looking string (`value=\"10.12\"`)\n * is accepted and parsed. Blank strings resolve to the unavailable dash rather\n * than throwing — `value=\"${maybeUndefined}\"` is a common template shape, and\n * `Number('')` is `0`, a silently wrong reading.\n */\nexport function assertReadoutValueType(\n tagName: string,\n value: number | string | null | undefined,\n valueType: ReadoutValueType\n): void {\n // `undefined`/`null` mean \"use the default\", matching how the components and\n // the resolvers treat an unset `valueType`.\n const resolved = valueType ?? ReadoutValueType.number;\n if (!isReadoutValueType(resolved)) {\n throw new TypeError(\n `<${tagName}>: valueType must be ` +\n `${READOUT_VALUE_TYPES.map((t) => `\"${t}\"`).join(' or ')} ` +\n `(got ${JSON.stringify(valueType)}).`\n );\n }\n if (\n resolved !== ReadoutValueType.number ||\n typeof value !== 'string' ||\n isBlank(value)\n ) {\n return;\n }\n if (Number.isFinite(Number(value))) {\n return;\n }\n throw new TypeError(\n `<${tagName}>: value must be a number when valueType is \"number\" ` +\n `(got ${JSON.stringify(value)}). Set valueType=\"text\" to render text.`\n );\n}\n\n/** The value as a number, or `undefined` when it is text / unavailable. */\nexport function resolveReadoutNumericValue(\n value: number | string | null | undefined,\n valueType: ReadoutValueType\n): number | undefined {\n if (\n valueType === ReadoutValueType.text ||\n value === null ||\n value === undefined\n ) {\n return undefined;\n }\n if (typeof value === 'number') {\n return value;\n }\n if (isBlank(value)) {\n return undefined;\n }\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\n/** The value as display text, or `undefined` when not in text mode / blank. */\nexport function resolveReadoutTextValue(\n value: number | string | null | undefined,\n valueType: ReadoutValueType\n): string | undefined {\n if (\n valueType !== ReadoutValueType.text ||\n value === null ||\n value === undefined\n ) {\n return undefined;\n }\n const text = typeof value === 'number' ? String(value) : value;\n return isBlank(text) ? undefined : text;\n}\n\nfunction dashedGenerator({\n showZeroPadding,\n minValueLength,\n fractionDigits,\n}: ReadoutNumericFormatOptions): string {\n const visibleDigits = showZeroPadding ? Math.max(minValueLength, 1) : 1;\n\n if (fractionDigits < 1) {\n return '-'.repeat(visibleDigits);\n }\n\n const integerDigits = visibleDigits - fractionDigits;\n\n return (\n '-'.repeat(Math.max(integerDigits, 1)) + '.' + '-'.repeat(fractionDigits)\n );\n}\n\nexport function formatNumericValue(\n value: number | undefined,\n options: ReadoutNumericFormatOptions\n): string {\n if (value === undefined) {\n return dashedGenerator(options);\n }\n\n return value.toFixed(options.fractionDigits);\n}\n\nexport function readoutFormattedInteger(valueText: string): number {\n const t = valueText.trim();\n if (!t) {\n return 0;\n }\n\n const rest = t.startsWith('-') ? t.slice(1) : t;\n const dot = rest.indexOf('.');\n return dot === -1 ? rest.length : dot;\n}\n\nexport function getHintZeros(\n value: number | undefined,\n {showZeroPadding, minValueLength, fractionDigits}: ReadoutNumericFormatOptions\n): string {\n const formattedValue = formatNumericValue(value, {\n showZeroPadding,\n minValueLength,\n fractionDigits,\n });\n const dotLength = fractionDigits > 0 ? 1 : 0;\n const integerLength = formattedValue.length - dotLength;\n const hintedDigits = Math.max(minValueLength - integerLength, 0);\n\n if (hintedDigits > 0) {\n return '0'.repeat(hintedDigits);\n }\n\n return '';\n}\n"],"names":["ReadoutValueType"],"mappings":"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"}
1
+ {"version":3,"file":"readout-formatters.js","sources":["../../../src/navigation-instruments/readout/readout-formatters.ts"],"sourcesContent":["export type ReadoutNumericFormatOptions = {\n showZeroPadding: boolean;\n minValueLength: number;\n fractionDigits: number;\n};\n\n/**\n * How a readout's `value` is interpreted.\n * - `number`: formatted via `fractionDigits` / `maxDigits`.\n * - `text`: rendered verbatim, with the numeric format options ignored.\n */\nexport enum ReadoutValueType {\n number = 'number',\n text = 'text',\n}\n\nconst READOUT_VALUE_TYPES: readonly string[] = Object.values(ReadoutValueType);\n\n/** Whether `value` is one of the supported {@link ReadoutValueType} values. */\nexport function isReadoutValueType(value: unknown): value is ReadoutValueType {\n return typeof value === 'string' && READOUT_VALUE_TYPES.includes(value);\n}\n\nfunction isBlank(value: string): boolean {\n return value.trim() === '';\n}\n\n/**\n * Throws when `valueType` is not a supported value, or when `value` is text but\n * `valueType` is `number`.\n *\n * `valueType` is validated first because an attribute carries an unchecked\n * string: a typo such as `valuetype=\"strng\"` matches neither mode, so every\n * mode check falls through and the readout silently renders the unavailable\n * dash — the opposite of the loud failure this contract exists to give.\n * `undefined`/`null` are allowed and mean \"use the default\".\n *\n * Attributes are always strings, so a numeric-looking string (`value=\"10.12\"`)\n * is accepted and parsed. Blank strings resolve to the unavailable dash rather\n * than throwing — `value=\"${maybeUndefined}\"` is a common template shape, and\n * `Number('')` is `0`, a silently wrong reading.\n */\nexport function assertReadoutValueType(\n tagName: string,\n value: number | string | null | undefined,\n valueType: ReadoutValueType\n): void {\n // `undefined`/`null` mean \"use the default\", matching how the components and\n // the resolvers treat an unset `valueType`.\n const resolved = valueType ?? ReadoutValueType.number;\n if (!isReadoutValueType(resolved)) {\n throw new TypeError(\n `<${tagName}>: valueType must be ` +\n `${READOUT_VALUE_TYPES.map((t) => `\"${t}\"`).join(' or ')} ` +\n `(got ${JSON.stringify(valueType)}).`\n );\n }\n if (\n resolved !== ReadoutValueType.number ||\n typeof value !== 'string' ||\n isBlank(value)\n ) {\n return;\n }\n if (Number.isFinite(Number(value))) {\n return;\n }\n throw new TypeError(\n `<${tagName}>: value must be a number when valueType is \"number\" ` +\n `(got ${JSON.stringify(value)}). Set valueType=\"text\" to render text.`\n );\n}\n\n/**\n * The value as a number, or `undefined` when it is text / unavailable.\n *\n * A non-finite number (`NaN`, `±Infinity`) counts as unavailable and renders the\n * dash, the same as `null`. `NaN` is a runtime data condition — a sensor\n * dropout, a `0/0`, a bad parse — not a programmer error, so it must not throw;\n * and `value.toFixed()` would otherwise render the literal text `\"NaN\"` /\n * `\"Infinity\"` in place of a reading. This also makes the number path agree with\n * the string path below, which has always resolved a non-finite string to\n * `undefined` — before this, `<obc-readout value=\"NaN\">` rendered a dash while\n * `.value=${NaN}` rendered `\"NaN\"`.\n */\nexport function resolveReadoutNumericValue(\n value: number | string | null | undefined,\n valueType: ReadoutValueType\n): number | undefined {\n if (\n valueType === ReadoutValueType.text ||\n value === null ||\n value === undefined\n ) {\n return undefined;\n }\n if (typeof value === 'number') {\n return Number.isFinite(value) ? value : undefined;\n }\n if (isBlank(value)) {\n return undefined;\n }\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\n/** The value as display text, or `undefined` when not in text mode / blank. */\nexport function resolveReadoutTextValue(\n value: number | string | null | undefined,\n valueType: ReadoutValueType\n): string | undefined {\n if (\n valueType !== ReadoutValueType.text ||\n value === null ||\n value === undefined\n ) {\n return undefined;\n }\n const text = typeof value === 'number' ? String(value) : value;\n return isBlank(text) ? undefined : text;\n}\n\n/**\n * The character used for an unavailable (\"no reading\") value.\n *\n * U+2012 FIGURE DASH, not the ASCII hyphen-minus: it is defined to be the same\n * width as a digit, so the placeholder lines up with the reading it stands in\n * for. Measured in Noto Sans with tabular figures at `size=\"m\"` — digit 13.02px,\n * U+2012 13.02px, U+002D 7.02px, en dash 11.02px, em dash 22.02px. With a\n * hyphen, `-.--` sat 46% narrow per character and its decimal point missed the\n * reading's; with U+2012 the point and every fraction position align exactly.\n */\nexport const READOUT_UNAVAILABLE_DASH = '\\u2012';\n\n/**\n * The unavailable (\"no reading\") text: a single integer dash plus one dash per\n * fraction digit — `\\u2012` at `fractionDigits` 0, `\\u2012.\\u2012\\u2012` at 2.\n *\n * Deliberately NOT filled out to `maxDigits`: the placeholder stays short and\n * sits at the right edge of the reserved width, rather than spelling out every\n * reserved digit position. `maxDigits` still reserves the width, so nothing\n * shifts when a reading arrives.\n */\nfunction dashedGenerator({\n showZeroPadding,\n minValueLength,\n fractionDigits,\n}: ReadoutNumericFormatOptions): string {\n const visibleDigits = showZeroPadding ? Math.max(minValueLength, 1) : 1;\n\n if (fractionDigits < 1) {\n return READOUT_UNAVAILABLE_DASH.repeat(visibleDigits);\n }\n\n const integerDigits = visibleDigits - fractionDigits;\n\n return (\n READOUT_UNAVAILABLE_DASH.repeat(Math.max(integerDigits, 1)) +\n '.' +\n READOUT_UNAVAILABLE_DASH.repeat(fractionDigits)\n );\n}\n\nexport function formatNumericValue(\n value: number | undefined,\n options: ReadoutNumericFormatOptions\n): string {\n // Non-finite counts as unavailable here too, not only in\n // `resolveReadoutNumericValue`. Every caller normalises today, but this\n // function is exported, and `NaN.toFixed()` would put the literal text\n // \"NaN\" where a reading belongs — the exact failure this change removes.\n if (value === undefined || !Number.isFinite(value)) {\n return dashedGenerator(options);\n }\n\n return value.toFixed(options.fractionDigits);\n}\n\nexport function readoutFormattedInteger(valueText: string): number {\n const t = valueText.trim();\n if (!t) {\n return 0;\n }\n\n const rest = t.startsWith('-') ? t.slice(1) : t;\n const dot = rest.indexOf('.');\n return dot === -1 ? rest.length : dot;\n}\n\nexport function getHintZeros(\n value: number | undefined,\n {showZeroPadding, minValueLength, fractionDigits}: ReadoutNumericFormatOptions\n): string {\n const formattedValue = formatNumericValue(value, {\n showZeroPadding,\n minValueLength,\n fractionDigits,\n });\n const dotLength = fractionDigits > 0 ? 1 : 0;\n const integerLength = formattedValue.length - dotLength;\n const hintedDigits = Math.max(minValueLength - integerLength, 0);\n\n if (hintedDigits > 0) {\n return '0'.repeat(hintedDigits);\n }\n\n return '';\n}\n"],"names":["ReadoutValueType"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"readout-shared.d.ts","sourceRoot":"","sources":["../../../src/navigation-instruments/readout/readout-shared.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,oBAAoB,EACrB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACtB,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GACrB,2BAA2B,CAM7B;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,aAAa,EAAE,2BAA2B,GACzC,OAAO,CAQT;AAED,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,GAAG,cAAc,CASzE;AAED,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,gBAAgB,GAAG,cAAc,CAS3E;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,OAAO,GAClB,oBAAoB,CAItB;AAED,8EAA8E;AAC9E,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,uBAAuB,GAAG,SAAS,GAC/C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,eAAe,EAAE,OAAO,EACxB,aAAa,EAAE,qBAAqB,GACnC,qBAAqB,CAEvB"}
1
+ {"version":3,"file":"readout-shared.d.ts","sourceRoot":"","sources":["../../../src/navigation-instruments/readout/readout-shared.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,oBAAoB,EACrB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,qBAAqB,EACtB,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EAEL,KAAK,2BAA2B,EACjC,MAAM,yBAAyB,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,wBAAgB,2BAA2B,CACzC,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GACrB,2BAA2B,CAY7B;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,aAAa,EAAE,2BAA2B,GACzC,OAAO,CAkBT;AAED,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,GAAG,cAAc,CASzE;AAED,0EAA0E;AAC1E,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,gBAAgB,GAAG,cAAc,CAS3E;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,OAAO,GAClB,oBAAoB,CAItB;AAED,8EAA8E;AAC9E,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,uBAAuB,GAAG,SAAS,GAC/C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,eAAe,EAAE,OAAO,EACxB,aAAa,EAAE,qBAAqB,GACnC,qBAAqB,CAEvB"}
@@ -3,6 +3,12 @@ import { ReadoutBlockSize, ReadoutBlockDataQuality, ReadoutBlockHidePhase } from
3
3
  import { formatNumericValue } from "./readout-formatters.js";
4
4
  function readoutNumericFormatOptions(maxDigits, fractionDigits) {
5
5
  return {
6
+ // Comparison-only (see `isDisplayedAtSetpoint`), which returns early unless
7
+ // both operands are finite numbers — so `formatNumericValue` never reaches
8
+ // its unavailable-dash branch here and the padding would have no effect.
9
+ // `obc-readout-block` sets it to `false` for rendering as well, since the
10
+ // unavailable placeholder is deliberately short rather than filled out to
11
+ // `maxDigits`. Nothing enables it today.
6
12
  showZeroPadding: false,
7
13
  minValueLength: maxDigits,
8
14
  fractionDigits
@@ -12,6 +18,9 @@ function isDisplayedAtSetpoint(value, setpoint, formatOptions) {
12
18
  if (value === null || setpoint === void 0) {
13
19
  return false;
14
20
  }
21
+ if (!Number.isFinite(value) || !Number.isFinite(setpoint)) {
22
+ return false;
23
+ }
15
24
  return formatNumericValue(value, formatOptions) === formatNumericValue(setpoint, formatOptions);
16
25
  }
17
26
  function readoutPrimarySize(size) {
@@ -1 +1 @@
1
- {"version":3,"file":"readout-shared.js","sources":["../../../src/navigation-instruments/readout/readout-shared.ts"],"sourcesContent":["import {\n ObcTextboxSize,\n ObcTextboxFontWeight,\n} from '../../components/textbox/textbox.js';\nimport {\n ReadoutBlockSize,\n ReadoutBlockDataQuality,\n ReadoutBlockHidePhase,\n} from '../../building-blocks/readout-block/readout-block.js';\nimport {\n formatNumericValue,\n type ReadoutNumericFormatOptions,\n} from './readout-formatters.js';\n\n/**\n * Pure helpers shared by `obc-readout` and `obc-readout-list-item`. The two\n * components are layout siblings built from the same primitives + per-block\n * options API (see #1012); this module keeps their behavioral logic in\n * lock-step until an eventual merge.\n *\n * ## Features\n * - **Numeric formatting:** {@link readoutNumericFormatOptions} builds the\n * shared `maxDigits`/`fractionDigits` format options (never zero-padded).\n * - **Setpoint comparison:** {@link isDisplayedAtSetpoint} compares the\n * DISPLAYED (rounded) value and setpoint strings, not the raw numbers.\n * - **Typography mapping:** {@link readoutPrimarySize} /\n * {@link readoutSecondarySize} map the density tier to the primary /\n * de-emphasised `obc-textbox` size; {@link readoutSetpointWeight} picks the\n * setpoint font weight from its emphasis state.\n * - **Data quality:** {@link readoutDataQualityClasses} produces the classMap\n * fragment for the low-integrity / invalid chip.\n * - **Pop-up hide phase:** {@link resolveSetpointHidePhase} maps the\n * component's deferred-hide state onto the block-level\n * `ReadoutBlockHidePhase`.\n *\n * ## Usage Guidelines\n * These helpers are consumed by the components' private getters — behavioral\n * changes belong here (once), not re-inlined per component.\n *\n * @example\n * const format = readoutNumericFormatOptions(3, 1);\n * const atSetpoint = isDisplayedAtSetpoint(29.99, 30, format);\n */\n\nexport function readoutNumericFormatOptions(\n maxDigits: number,\n fractionDigits: number\n): ReadoutNumericFormatOptions {\n return {\n showZeroPadding: false,\n minValueLength: maxDigits,\n fractionDigits,\n };\n}\n\n/**\n * Whether the value reads as \"at the setpoint\". Compares what is DISPLAYED\n * (rounded to fractionDigits), not the raw values, so e.g. 29.999 and 30 at\n * fractionDigits=0 both read \"30\" → at setpoint.\n */\nexport function isDisplayedAtSetpoint(\n value: number | null,\n setpoint: number | undefined,\n formatOptions: ReadoutNumericFormatOptions\n): boolean {\n if (value === null || setpoint === undefined) {\n return false;\n }\n return (\n formatNumericValue(value, formatOptions) ===\n formatNumericValue(setpoint, formatOptions)\n );\n}\n\n/** Primary value-typography size for a density tier. */\nexport function readoutPrimarySize(size: ReadoutBlockSize): ObcTextboxSize {\n switch (size) {\n case ReadoutBlockSize.large:\n return ObcTextboxSize.l;\n case ReadoutBlockSize.medium:\n return ObcTextboxSize.m;\n default:\n return ObcTextboxSize.s;\n }\n}\n\n/** Secondary (de-emphasised) value-typography size for a density tier. */\nexport function readoutSecondarySize(size: ReadoutBlockSize): ObcTextboxSize {\n switch (size) {\n case ReadoutBlockSize.large:\n return ObcTextboxSize.s;\n case ReadoutBlockSize.medium:\n return ObcTextboxSize.s;\n default:\n return ObcTextboxSize.xs;\n }\n}\n\n/** Setpoint is SemiBold only while emphasised, otherwise regular weight. */\nexport function readoutSetpointWeight(\n emphasized: boolean\n): ObcTextboxFontWeight {\n return emphasized\n ? ObcTextboxFontWeight.semibold\n : ObcTextboxFontWeight.regular;\n}\n\n/** classMap fragment for a block / source carrying per-block data quality. */\nexport function readoutDataQualityClasses(\n dataQuality: ReadoutBlockDataQuality | undefined\n): Record<string, boolean> {\n return {\n 'data-low-integrity': dataQuality === ReadoutBlockDataQuality.lowIntegrity,\n 'data-invalid': dataQuality === ReadoutBlockDataQuality.invalid,\n };\n}\n\n/**\n * The block-level hide phase for a pop-up setpoint: the component's deferred\n * phase while the value sits at the setpoint, `none` otherwise (value away\n * from the setpoint, or touching).\n */\nexport function resolveSetpointHidePhase(\n popUpAtSetpoint: boolean,\n deferredPhase: ReadoutBlockHidePhase\n): ReadoutBlockHidePhase {\n return popUpAtSetpoint ? deferredPhase : ReadoutBlockHidePhase.none;\n}\n"],"names":[],"mappings":";;;AA4CO,SAAS,4BACd,WACA,gBAC6B;AAC7B,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB;AAAA,EAAA;AAEJ;AAOO,SAAS,sBACd,OACA,UACA,eACS;AACT,MAAI,UAAU,QAAQ,aAAa,QAAW;AAC5C,WAAO;AAAA,EACT;AACA,SACE,mBAAmB,OAAO,aAAa,MACvC,mBAAmB,UAAU,aAAa;AAE9C;AAGO,SAAS,mBAAmB,MAAwC;AACzE,UAAQ,MAAA;AAAA,IACN,KAAK,iBAAiB;AACpB,aAAO,eAAe;AAAA,IACxB,KAAK,iBAAiB;AACpB,aAAO,eAAe;AAAA,IACxB;AACE,aAAO,eAAe;AAAA,EAAA;AAE5B;AAGO,SAAS,qBAAqB,MAAwC;AAC3E,UAAQ,MAAA;AAAA,IACN,KAAK,iBAAiB;AACpB,aAAO,eAAe;AAAA,IACxB,KAAK,iBAAiB;AACpB,aAAO,eAAe;AAAA,IACxB;AACE,aAAO,eAAe;AAAA,EAAA;AAE5B;AAGO,SAAS,sBACd,YACsB;AACtB,SAAO,aACH,qBAAqB,WACrB,qBAAqB;AAC3B;AAGO,SAAS,0BACd,aACyB;AACzB,SAAO;AAAA,IACL,sBAAsB,gBAAgB,wBAAwB;AAAA,IAC9D,gBAAgB,gBAAgB,wBAAwB;AAAA,EAAA;AAE5D;AAOO,SAAS,yBACd,iBACA,eACuB;AACvB,SAAO,kBAAkB,gBAAgB,sBAAsB;AACjE;"}
1
+ {"version":3,"file":"readout-shared.js","sources":["../../../src/navigation-instruments/readout/readout-shared.ts"],"sourcesContent":["import {\n ObcTextboxSize,\n ObcTextboxFontWeight,\n} from '../../components/textbox/textbox.js';\nimport {\n ReadoutBlockSize,\n ReadoutBlockDataQuality,\n ReadoutBlockHidePhase,\n} from '../../building-blocks/readout-block/readout-block.js';\nimport {\n formatNumericValue,\n type ReadoutNumericFormatOptions,\n} from './readout-formatters.js';\n\n/**\n * Pure helpers shared by `obc-readout` and `obc-readout-list-item`. The two\n * components are layout siblings built from the same primitives + per-block\n * options API (see #1012); this module keeps their behavioral logic in\n * lock-step until an eventual merge.\n *\n * ## Features\n * - **Numeric formatting:** {@link readoutNumericFormatOptions} builds the\n * shared `maxDigits`/`fractionDigits` format options (never zero-padded).\n * - **Setpoint comparison:** {@link isDisplayedAtSetpoint} compares the\n * DISPLAYED (rounded) value and setpoint strings, not the raw numbers.\n * - **Typography mapping:** {@link readoutPrimarySize} /\n * {@link readoutSecondarySize} map the density tier to the primary /\n * de-emphasised `obc-textbox` size; {@link readoutSetpointWeight} picks the\n * setpoint font weight from its emphasis state.\n * - **Data quality:** {@link readoutDataQualityClasses} produces the classMap\n * fragment for the low-integrity / invalid chip.\n * - **Pop-up hide phase:** {@link resolveSetpointHidePhase} maps the\n * component's deferred-hide state onto the block-level\n * `ReadoutBlockHidePhase`.\n *\n * ## Usage Guidelines\n * These helpers are consumed by the components' private getters — behavioral\n * changes belong here (once), not re-inlined per component.\n *\n * @example\n * const format = readoutNumericFormatOptions(3, 1);\n * const atSetpoint = isDisplayedAtSetpoint(29.99, 30, format);\n */\n\nexport function readoutNumericFormatOptions(\n maxDigits: number,\n fractionDigits: number\n): ReadoutNumericFormatOptions {\n return {\n // Comparison-only (see `isDisplayedAtSetpoint`), which returns early unless\n // both operands are finite numbers — so `formatNumericValue` never reaches\n // its unavailable-dash branch here and the padding would have no effect.\n // `obc-readout-block` sets it to `false` for rendering as well, since the\n // unavailable placeholder is deliberately short rather than filled out to\n // `maxDigits`. Nothing enables it today.\n showZeroPadding: false,\n minValueLength: maxDigits,\n fractionDigits,\n };\n}\n\n/**\n * Whether the value reads as \"at the setpoint\". Compares what is DISPLAYED\n * (rounded to fractionDigits), not the raw values, so e.g. 29.999 and 30 at\n * fractionDigits=0 both read \"30\" → at setpoint.\n */\nexport function isDisplayedAtSetpoint(\n value: number | null,\n setpoint: number | undefined,\n formatOptions: ReadoutNumericFormatOptions\n): boolean {\n if (value === null || setpoint === undefined) {\n return false;\n }\n // An unavailable reading is never \"at\" the setpoint. Callers normalise\n // `value` (via `resolveReadoutNumericValue`) but pass `setpoint` raw, so a\n // non-finite setpoint would otherwise be formatted as the literal \"NaN\" /\n // \"Infinity\" and compared as a string. The comparison result happens to be\n // correct either way — a normalised `value` can never also format to \"NaN\" —\n // but guarding both keeps the two operands symmetric and the invariant below\n // honest.\n if (!Number.isFinite(value) || !Number.isFinite(setpoint)) {\n return false;\n }\n return (\n formatNumericValue(value, formatOptions) ===\n formatNumericValue(setpoint, formatOptions)\n );\n}\n\n/** Primary value-typography size for a density tier. */\nexport function readoutPrimarySize(size: ReadoutBlockSize): ObcTextboxSize {\n switch (size) {\n case ReadoutBlockSize.large:\n return ObcTextboxSize.l;\n case ReadoutBlockSize.medium:\n return ObcTextboxSize.m;\n default:\n return ObcTextboxSize.s;\n }\n}\n\n/** Secondary (de-emphasised) value-typography size for a density tier. */\nexport function readoutSecondarySize(size: ReadoutBlockSize): ObcTextboxSize {\n switch (size) {\n case ReadoutBlockSize.large:\n return ObcTextboxSize.s;\n case ReadoutBlockSize.medium:\n return ObcTextboxSize.s;\n default:\n return ObcTextboxSize.xs;\n }\n}\n\n/** Setpoint is SemiBold only while emphasised, otherwise regular weight. */\nexport function readoutSetpointWeight(\n emphasized: boolean\n): ObcTextboxFontWeight {\n return emphasized\n ? ObcTextboxFontWeight.semibold\n : ObcTextboxFontWeight.regular;\n}\n\n/** classMap fragment for a block / source carrying per-block data quality. */\nexport function readoutDataQualityClasses(\n dataQuality: ReadoutBlockDataQuality | undefined\n): Record<string, boolean> {\n return {\n 'data-low-integrity': dataQuality === ReadoutBlockDataQuality.lowIntegrity,\n 'data-invalid': dataQuality === ReadoutBlockDataQuality.invalid,\n };\n}\n\n/**\n * The block-level hide phase for a pop-up setpoint: the component's deferred\n * phase while the value sits at the setpoint, `none` otherwise (value away\n * from the setpoint, or touching).\n */\nexport function resolveSetpointHidePhase(\n popUpAtSetpoint: boolean,\n deferredPhase: ReadoutBlockHidePhase\n): ReadoutBlockHidePhase {\n return popUpAtSetpoint ? deferredPhase : ReadoutBlockHidePhase.none;\n}\n"],"names":[],"mappings":";;;AA4CO,SAAS,4BACd,WACA,gBAC6B;AAC7B,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOL,iBAAiB;AAAA,IACjB,gBAAgB;AAAA,IAChB;AAAA,EAAA;AAEJ;AAOO,SAAS,sBACd,OACA,UACA,eACS;AACT,MAAI,UAAU,QAAQ,aAAa,QAAW;AAC5C,WAAO;AAAA,EACT;AAQA,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,CAAC,OAAO,SAAS,QAAQ,GAAG;AACzD,WAAO;AAAA,EACT;AACA,SACE,mBAAmB,OAAO,aAAa,MACvC,mBAAmB,UAAU,aAAa;AAE9C;AAGO,SAAS,mBAAmB,MAAwC;AACzE,UAAQ,MAAA;AAAA,IACN,KAAK,iBAAiB;AACpB,aAAO,eAAe;AAAA,IACxB,KAAK,iBAAiB;AACpB,aAAO,eAAe;AAAA,IACxB;AACE,aAAO,eAAe;AAAA,EAAA;AAE5B;AAGO,SAAS,qBAAqB,MAAwC;AAC3E,UAAQ,MAAA;AAAA,IACN,KAAK,iBAAiB;AACpB,aAAO,eAAe;AAAA,IACxB,KAAK,iBAAiB;AACpB,aAAO,eAAe;AAAA,IACxB;AACE,aAAO,eAAe;AAAA,EAAA;AAE5B;AAGO,SAAS,sBACd,YACsB;AACtB,SAAO,aACH,qBAAqB,WACrB,qBAAqB;AAC3B;AAGO,SAAS,0BACd,aACyB;AACzB,SAAO;AAAA,IACL,sBAAsB,gBAAgB,wBAAwB;AAAA,IAC9D,gBAAgB,gBAAgB,wBAAwB;AAAA,EAAA;AAE5D;AAOO,SAAS,yBACd,iBACA,eACuB;AACvB,SAAO,kBAAkB,gBAAgB,sBAAsB;AACjE;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oicl/openbridge-webcomponents-full-bundle",
3
- "version": "2.0.0-next.115",
3
+ "version": "2.0.0-next.116",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,6 +10,7 @@ import {
10
10
  ReadoutValueType,
11
11
  } from './readout-block.js';
12
12
  import './readout-block.js';
13
+ import '../../components/textbox/textbox.js';
13
14
  import '../../icons/icon-placeholder.js';
14
15
  import {
15
16
  ObcAlertFrameMode,
@@ -41,11 +42,18 @@ type BlockArgs = {
41
42
  // A faithful single-block render. The block inherits its colour from the host
42
43
  // context (the list-item normally drives it), so standalone it shows the neutral
43
44
  // default tone; `enhanced` switches to the accent tone.
45
+ //
46
+ // `value` is passed through as given, deliberately NOT as `args.value ?? null`:
47
+ // one unavailable case IS `undefined`, and coercing here would quietly turn it
48
+ // into the `null` case. `undefined` sits outside the declared `value` type —
49
+ // consumers should pass `null` — but an unset property arrives as `undefined` in
50
+ // practice and has to read the same, so the cast is what the case is testing
51
+ // rather than a way around the type checker.
44
52
  function renderBlock(args: Partial<BlockArgs>) {
45
53
  return html`
46
54
  <obc-readout-block
47
55
  .variant=${args.variant ?? ReadoutBlockVariant.value}
48
- .value=${args.value ?? null}
56
+ .value=${args.value as number | string | null}
49
57
  .valueType=${args.valueType ?? ReadoutValueType.number}
50
58
  .size=${args.size ?? ReadoutBlockSize.small}
51
59
  .enhanced=${args.enhanced ?? false}
@@ -415,6 +423,240 @@ export const TestValidationSurvivesUnrelatedUpdate: Story = {
415
423
  },
416
424
  };
417
425
 
426
+ // The designer's specification, revised in review: the unavailable placeholder
427
+ // stays SHORT (`-.--`, not `---.--`) and sits at the right of the reserved width.
428
+ // format: 000.00 · readout: 12.30 · hinted: 012.30 · not available: -.--
429
+ // `format: 000.00` maps to maxDigits 3 + fractionDigits 2.
430
+ const DESIGNER_SPEC_CASES: {
431
+ label: string;
432
+ expected: string;
433
+ args: Partial<BlockArgs>;
434
+ }[] = [
435
+ {
436
+ label: 'readout',
437
+ expected: '12.30',
438
+ args: {value: 12.3, maxDigits: 3, fractionDigits: 2},
439
+ },
440
+ {
441
+ label: 'readout with hinted',
442
+ expected: '012.30',
443
+ args: {value: 12.3, maxDigits: 3, fractionDigits: 2, hintedZeros: true},
444
+ },
445
+ {
446
+ label: 'not available',
447
+ expected: '-.--',
448
+ args: {value: null, maxDigits: 3, fractionDigits: 2},
449
+ },
450
+ {
451
+ label: 'not available, hinted enabled',
452
+ expected: '-.--',
453
+ args: {value: null, maxDigits: 3, fractionDigits: 2, hintedZeros: true},
454
+ },
455
+ ];
456
+
457
+ // Every input that reads as "no reading". All four of the first rows are the
458
+ // same placeholder — that IS the point: whichever way a reading goes missing,
459
+ // the readout looks identical.
460
+ const UNAVAILABLE_CASES: {
461
+ label: string;
462
+ args: Partial<BlockArgs>;
463
+ }[] = [
464
+ {
465
+ label: 'NaN',
466
+ args: {value: Number.NaN, maxDigits: 3, fractionDigits: 2},
467
+ },
468
+ {
469
+ label: 'Infinity',
470
+ args: {value: Number.POSITIVE_INFINITY, maxDigits: 3, fractionDigits: 2},
471
+ },
472
+ {
473
+ label: 'null',
474
+ args: {value: null, maxDigits: 3, fractionDigits: 2},
475
+ },
476
+ {
477
+ label: 'undefined — an unset value, same as null',
478
+ args: {value: undefined, maxDigits: 3, fractionDigits: 2},
479
+ },
480
+ {
481
+ label: 'null · no fraction digits',
482
+ args: {value: null, maxDigits: 3},
483
+ },
484
+ ];
485
+
486
+ // Stacked in one column under identical settings so the decimal points and the
487
+ // fraction positions can be checked against each other by eye.
488
+ const ALIGNMENT_CASES: Partial<BlockArgs>[] = [
489
+ {value: 12.3, maxDigits: 3, fractionDigits: 2},
490
+ {value: 4.5, maxDigits: 3, fractionDigits: 2},
491
+ {value: null, maxDigits: 3, fractionDigits: 2},
492
+ {value: Number.NaN, maxDigits: 3, fractionDigits: 2},
493
+ ];
494
+
495
+ /**
496
+ * **Unavailable values — for design review.**
497
+ *
498
+ * 1. **The placeholder is short.** `-.--` for `format: 000.00`, not `---.--`:
499
+ * `maxDigits` already reserves the width, so the dash sits at the right edge
500
+ * of the reserve rather than spelling out every reserved digit position.
501
+ * 2. **The dash is digit-width.** It is U+2012 FIGURE DASH, not the ASCII
502
+ * hyphen-minus. Measured in Noto Sans with tabular figures, a digit is
503
+ * 13.02px and U+2012 is 13.02px, while U+002D is 7.02px — so with a hyphen
504
+ * the placeholder's decimal point missed the reading's. `tabular-nums` does
505
+ * not help here: it equalises figures with each other and leaves punctuation
506
+ * untouched (measured identical with the feature on and off).
507
+ * 3. **`NaN` and `±Infinity` count as unavailable.** They previously rendered as
508
+ * the literal text `NaN` / `Infinity` in place of a reading. They are a
509
+ * runtime data condition (sensor dropout, `0/0`, a bad parse) rather than a
510
+ * programmer error, so they resolve to the dash rather than throwing.
511
+ *
512
+ * Hinted zeros are suppressed for an unavailable value, so the two "not
513
+ * available" rows are identical and nothing reads `----Na.N`.
514
+ *
515
+ * **Open question — dash treatment.** U+2012 is exactly digit-width, which is
516
+ * what makes the columns line up, but two adjacent dashes then run together
517
+ * into one bar. The last section renders the alternatives at a readable size:
518
+ * (A) the figure dash as implemented, (B) an ASCII hyphen centred in a
519
+ * digit-width cell — separated and still aligned, at the cost of a lighter,
520
+ * sparser look — and (C) the previous plain hyphen, for reference. Delete that
521
+ * section once the choice is made.
522
+ */
523
+ export const UnavailableValues: Story = {
524
+ render: () => html`
525
+ <style>
526
+ .rb-unavail {
527
+ display: grid;
528
+ grid-template-columns: max-content max-content max-content;
529
+ gap: 8px 24px;
530
+ align-items: center;
531
+ }
532
+ .rb-unavail-2col {
533
+ grid-template-columns: max-content max-content;
534
+ }
535
+ .rb-unavail-head {
536
+ font: 10px/1.2 var(--global-typography-ui-label-font-family, sans-serif);
537
+ text-transform: uppercase;
538
+ letter-spacing: 0.06em;
539
+ color: var(--element-neutral-color, #777);
540
+ }
541
+ .rb-unavail-label {
542
+ font: 12px/1.4 var(--global-typography-ui-label-font-family, sans-serif);
543
+ color: var(--element-neutral-color, #777);
544
+ }
545
+ .rb-unavail-spec {
546
+ margin: 20px 0 8px;
547
+ font: 12px/1.2 var(--global-typography-ui-label-font-family, sans-serif);
548
+ text-transform: uppercase;
549
+ letter-spacing: 0.06em;
550
+ color: var(--element-neutral-color, #777);
551
+ }
552
+ .rb-unavail-spec-value {
553
+ font:
554
+ 13px/1.4 ui-monospace,
555
+ monospace;
556
+ color: var(--element-neutral-color, #777);
557
+ }
558
+ .rb-unavail-now {
559
+ outline: 1px dashed rgba(0, 0, 0, 0.12);
560
+ width: max-content;
561
+ }
562
+ .rb-dash-options {
563
+ display: flex;
564
+ flex-direction: column;
565
+ gap: 8px;
566
+ width: max-content;
567
+ }
568
+ .rb-cellspan {
569
+ display: inline-block;
570
+ width: 1ch;
571
+ text-align: center;
572
+ }
573
+ /* One column, so the decimal points can be compared down the stack. */
574
+ .rb-align {
575
+ display: flex;
576
+ flex-direction: column;
577
+ align-items: flex-start;
578
+ gap: 2px;
579
+ width: max-content;
580
+ outline: 1px dashed rgba(0, 0, 0, 0.12);
581
+ }
582
+ </style>
583
+ <div class="rb-unavail-spec">Designer specification — format 000.00</div>
584
+ <div class="rb-unavail">
585
+ <div class="rb-unavail-head">Case</div>
586
+ <div class="rb-unavail-head">Specified</div>
587
+ <div class="rb-unavail-head">Rendered</div>
588
+ ${DESIGNER_SPEC_CASES.map(
589
+ (c) => html`
590
+ <div class="rb-unavail-label">${c.label}</div>
591
+ <div class="rb-unavail-spec-value">${c.expected}</div>
592
+ <div class="rb-unavail-now">
593
+ ${renderBlock({size: ReadoutBlockSize.medium, ...c.args})}
594
+ </div>
595
+ `
596
+ )}
597
+ </div>
598
+
599
+ <div class="rb-unavail-spec">
600
+ Alignment — decimal points and fraction positions line up
601
+ </div>
602
+ <div class="rb-align">
603
+ ${ALIGNMENT_CASES.map((args) =>
604
+ renderBlock({size: ReadoutBlockSize.medium, ...args})
605
+ )}
606
+ </div>
607
+
608
+ <div class="rb-unavail-spec">
609
+ Dash treatment — open question for the designer
610
+ </div>
611
+ <div class="rb-dash-options">
612
+ <div>
613
+ <div class="rb-unavail-label">reading, for reference</div>
614
+ <obc-textbox size="l" .tabularNums=${true}>12.30</obc-textbox>
615
+ </div>
616
+ <div>
617
+ <div class="rb-unavail-label">
618
+ A — U+2012 figure dash (implemented): digit-width, but adjacent dashes
619
+ run together
620
+ </div>
621
+ <obc-textbox size="l" .tabularNums=${true}
622
+ >&#8210;.&#8210;&#8210;</obc-textbox
623
+ >
624
+ </div>
625
+ <div>
626
+ <div class="rb-unavail-label">
627
+ B — hyphen in digit-width cells: separated, but lighter and sparser
628
+ </div>
629
+ <obc-textbox size="l" .tabularNums=${true}>
630
+ <span class="rb-cellspan">-</span>.<span class="rb-cellspan">-</span
631
+ ><span class="rb-cellspan">-</span>
632
+ </obc-textbox>
633
+ </div>
634
+ <div>
635
+ <div class="rb-unavail-label">
636
+ C — ASCII hyphen (previous): narrower than a digit, does not align
637
+ </div>
638
+ <obc-textbox size="l" .tabularNums=${true}>-.--</obc-textbox>
639
+ </div>
640
+ </div>
641
+
642
+ <div class="rb-unavail-spec">
643
+ Unavailable values — every unreadable input
644
+ </div>
645
+ <div class="rb-unavail rb-unavail-2col">
646
+ <div class="rb-unavail-head">Value</div>
647
+ <div class="rb-unavail-head">Rendered</div>
648
+ ${UNAVAILABLE_CASES.map(
649
+ (c) => html`
650
+ <div class="rb-unavail-label">${c.label}</div>
651
+ <div class="rb-unavail-now">
652
+ ${renderBlock({size: ReadoutBlockSize.medium, ...c.args})}
653
+ </div>
654
+ `
655
+ )}
656
+ </div>
657
+ `,
658
+ };
659
+
418
660
  export const DataQuality: Story = {
419
661
  render: () =>
420
662
  renderShowcase([