Skip to content

Operation Contracts

Grid Layout Plus uses discriminated unions for operations and interactions. Check status, reason, and structured error fields instead of matching message text.

LayoutOperationResult

Headless operations and component transaction proposals use this result:

ts
type LayoutOperationResult =
  | (LayoutOperationResultBase & {
      status: 'accepted'
      reason: 'applied'
    })
  | (LayoutOperationResultBase & {
      status: 'unchanged'
      reason: 'same-value'
    })
  | (LayoutOperationResultBase & {
      status: 'rejected'
      reason: LayoutOperationReason
    })

interface LayoutOperationResultBase {
  operation: 'set' | 'move' | 'resize' | 'add' | 'remove' | 'layer'
  id: LayoutItem['i'] | null
  previousLayout: ReadonlyLayout
  layout: ReadonlyLayout
  candidate: ReadonlyLayoutItem | null
}

Every Layout in the result is an independent readonly snapshot. accepted means the headless operation committed. unchanged is successful but produced no semantic change. rejected leaves the previous Layout committed.

LayoutTransactionReceipt

Methods on a rendered GridLayout return a controlled receipt:

ts
type LayoutTransactionReceipt =
  | {
      status: 'pending'
      revision: number
      proposal: AcceptedLayoutOperationResult
    }
  | Extract<LayoutOperationResult, { status: 'unchanged' | 'rejected' }>

pending is not a committed result. It means the component emitted the proposal and is waiting to receive it through layout and, in responsive mode, responsiveLayouts. Listen to layout-updated for the committed notification.

Rejection reasons

ReasonMeaning
item-not-foundNo item has the requested id.
interaction-activeAnother interaction prevents this operation.
static-itemThe target item is static.
disabledThe operation is disabled by the effective configuration.
collisionCollision prevention rejected the candidate.
out-of-boundsThe candidate cannot fit the configured columns.
max-rowsThe candidate exceeds maxRows.
invalid-inputAn argument or Layout does not satisfy the public contract.
external-updateAn external Layout update interrupted an interaction.
external-not-committedThe parent did not write a controlled proposal back.
supersededA newer proposal replaced the pending one.
config-changedA relevant configuration change cancelled the operation.
cancelledThe operation or interaction was cancelled.
extension-errorA custom extension threw while it was evaluated.
extension-invalid-resultA custom extension returned a value outside its contract.

Drop evaluation can additionally use callback-rejected and no-position.

LayoutUpdateMeta

ts
interface LayoutUpdateMeta {
  revision: number
  source:
    | 'interaction'
    | 'programmatic'
    | 'responsive'
    | 'width'
    | 'config'
    | 'external'
    | 'drop-commit'
}

Use revision to correlate events from one transaction. In responsive mode, update:layout and update:responsive-layouts carry the same revision.

OperationRejectedPayload

ts
interface OperationRejectedPayload {
  revision: number | null
  evaluationId: number
  operation: LayoutOperationResultBase['operation'] | 'config' | 'drop'
  reason: OperationRejectedReason
  id: LayoutItem['i'] | null
  previousLayout: ReadonlyLayout
  layout: ReadonlyLayout
  candidate: ReadonlyLayoutItem | Readonly<Omit<LayoutItem, 'i' | 'moved'>> | null
  nativeEvent: Event | null
}

operation-rejected reports rejected component commands, interaction candidates, controlled confirmations, configuration changes, and drop candidates. Receiving this payload does not mean the committed Layout has changed.

InteractionTerminalPayload

interaction-end and useGridLayout.onInteractionEnd finish with one of these states:

StatusMeaning
committedThe final candidate committed with reason: 'applied'.
unchangedThe interaction ended at the original Layout.
cancelledThe interaction was interrupted; inspect the typed reason.

The payload includes previousLayout, final layout, oldItem, final item, revision, and the last native event when available.

Errors

Invalid initial Layout or configuration throws synchronously. Stable Core API functions throw one of these classes:

ts
class GridLayoutValidationError extends TypeError {
  code: 'invalid-layout' | 'invalid-config'
  path: string
  cause: unknown
}

class GridLayoutExtensionError extends Error {
  code: 'extension-error' | 'extension-invalid-result'
  source: 'compactor' | 'position-strategy' | 'drop-config'
  path: string | null
  cause: unknown
}

After initialization, rendered components and composables report recoverable failures as GridLayoutRuntimeError while retaining the last valid state:

ts
interface GridLayoutRuntimeError {
  code:
    | 'invalid-layout'
    | 'invalid-config'
    | 'partial-responsive-update'
    | 'invalid-registration'
    | 'extension-error'
    | 'extension-invalid-result'
    | 'derived-geometry-overflow'
  source:
    | 'layout'
    | 'config'
    | 'container-width'
    | 'geometry'
    | 'compactor'
    | 'position-strategy'
    | 'drop-config'
    | 'grid-item'
  path: string | null
  revision: number | null
  evaluationId: number
  cause: unknown
}

Use code, source, path, and revision for program logic. Treat cause and human-readable messages as diagnostic information.

Released under the MIT License.