diff --git a/.changeset/7443-datetime-compact-style.md b/.changeset/7443-datetime-compact-style.md index c7190f6f2..70900b838 100644 --- a/.changeset/7443-datetime-compact-style.md +++ b/.changeset/7443-datetime-compact-style.md @@ -13,8 +13,22 @@ The cell now reads `field.format` (it destructured `value` only, so a `datetime` field could not reach the style vocabulary a `date` field has) and renders through the shared function, and `data-table`'s `formatCellValue` calls `formatDateTime` instead of a third, independently authored option bag. -Every existing cell renders byte-identically; `'compact'` is today's face -named and rehoused, not a new one. +Every existing cell without an authored `format`, and every cell authoring +`'compact'`, renders byte-identically; `'compact'` is today's face named and +rehoused, not a new one. A `datetime` field that authors any OTHER non-empty +`format` does change: the cell previously ignored `field` altogether and always +painted the compact face, and now anything other than `'compact'` selects the +verbose `formatDateTime` default — measured as `Jul 4, 2024, 07:00 AM` in +`en-US` for the instant whose compact face is `7/4/2024 7:00 am`. An +unrecognised value is neither rejected nor passed through; it silently lands on +that verbose face. No `datetime` field in this repository authors a `format`, so +no cell here moves — a consumer that authored one is the case this sentence is +for. Note that `format` has no declared value vocabulary to check a value +against: `@object-ui/types` types it `format?: string`, and `@objectstack/spec` +carries one free-form `format?: string` on its shared field schema, described +"Format string (e.g. email, phone)" and accepting any string. `'compact'` is +therefore the only value with a defined `datetime` meaning, and every other +value means "the verbose face" by fallthrough rather than by design. Additive, no signature change: `formatDateTime(value, options?)` is unchanged and `formatDateTime(v, { locale })` keeps meaning what it meant. diff --git a/.changeset/7747-datetime-format-cast-narrowing.md b/.changeset/7747-datetime-format-cast-narrowing.md new file mode 100644 index 000000000..b9f09b92e --- /dev/null +++ b/.changeset/7747-datetime-format-cast-narrowing.md @@ -0,0 +1,19 @@ +--- +--- + +Narrow the `DateTimeCellRenderer` style cast from `as any` to the subtype that +actually declares the property (objectui#7747). + +`const style = (field as any)?.format || 'compact'` becomes +`(field as DateTimeFieldMetadata | undefined)?.format`. Some cast is +load-bearing — `FieldMetadata` is a 37-member union and `BaseFieldMetadata` +carries no `format`, so the bare read is `TS2339` — but `as any` was wider than +the job: `DateTimeFieldMetadata` is exported from `@object-ui/types`, which this +file already imports from, so the narrow cast was available at zero cost. The +difference that buys: `as any` would also have silenced a typo in the property +name, and the narrow cast will not. + +Type-level only; no package is released by this change. The emitted expression +is identical, so every cell renders exactly what it rendered before — +`'compact'` and an absent or empty `format` keep the compact face, and any +other value keeps selecting the verbose `formatDateTime` default. diff --git a/packages/fields/src/index.tsx b/packages/fields/src/index.tsx index bb0974998..c16ba226e 100644 --- a/packages/fields/src/index.tsx +++ b/packages/fields/src/index.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import type { FieldMetadata, SelectOptionMetadata } from '@object-ui/types'; +import type { DateTimeFieldMetadata, FieldMetadata, SelectOptionMetadata } from '@object-ui/types'; import { ComponentRegistry, percentDisplayValue, getRecordDisplayName, humanizeLabel, isMissingForRequired, formatDate, formatDateTime, formatDateTimeCompactParts, formatRelativeDate, type ComponentMeta, type DateDisplayOptions } from '@object-ui/core'; // The platform's own value-shape contract, asked rather than restated // (objectui#6744). See `locationStoredValueSchemaFor` below for why this is a @@ -882,7 +882,11 @@ export function DateTimeCellRenderer({ value, field }: CellRendererProps): React // (objectui#7443). `||`, not `??`, matches the `date` cell and keeps an // authored empty string on the compact face rather than dropping it into // the verbose default. - const style = (field as any)?.format || 'compact'; + // `FieldMetadata` is a 37-member union and `BaseFieldMetadata` carries no + // `format`, so the bare property read is `TS2339` — SOME cast is load-bearing. + // `DateTimeFieldMetadata` is the narrowest one that carries it (objectui#7747); + // `as any` would also silence a typo in the property name, this does not. + const style = (field as DateTimeFieldMetadata | undefined)?.format || 'compact'; // The compact face is painted in two halves — the time is muted and offset // — so this branch asks the shared module for the halves rather than the