Mixins / foldkit-mixins-ui

CascadeSelect

namespace · packages/mixins-ui/src/cascadeSelect.ts:1

export * as CascadeSelect from './cascadeSelect.js'

In the guides

Members

CascadeSelect.CascadeSelectSlots

const · packages/mixins-ui/src/cascadeSelect.ts:39

const CascadeSelectSlots: Slots.Contract<{ readonly root: Slot.UnnamedSlot<Capability.Capability<"Container", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>; readonly trigger: Slot.UnnamedSlot<Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>, readonly [Event.EventToken<"click">, Event.EventToken<"keydown">], readonly [Attr.AttrToken<"role">, Attr.AttrToken<"aria-label">, Attr.AttrToken<"aria-expanded">, Attr.AttrToken<"aria-invalid">, Attr.AttrToken<"disabled">], readonly Slot.RequirementName[], boolean>; readonly valueText: Slot.UnnamedSlot<Capability.Capability<"Container", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>; readonly chevron: Slot.UnnamedSlot<Capability.Capability<"Container", readonly [Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>]>, readonly Slot.EventName[], readonly Slot.AttributeName[], readonly Slot.RequirementName[], boolean>; readonly clear: Slot.UnnamedSlot<Capability.Capability<"Interactive", readonly [Capability.Capability<"Base", readonly []>]>, readonly [Event.EventToken<"click">], readonly [Attr.AttrToken<"aria-label">], readonly Slot.RequirementName[], boolean>; readonly inp …;

A cascade select, Zag's: one value chosen as a path through nested options (a country, then a region, then a city), from a popup that shows one column per level, or one level at a time with a way back (layout: 'drill'). The value is the path of ids, root first.

trigger -> Opened -> highlighted = the value's path, or the first option
keys, pointer -> Pressed / Highlighted / Picked -> update(config) -> highlighted, value
view: highlighted -> the columns shown, the active row; value -> the trigger's text

The open popup is a tree in the APG's sense, its columns the levels: focus rests on it and aria-activedescendant names the active row. The arrows move within a level and between levels (ArrowRight into the active row's children, ArrowLeft back to its parent, swapped right to left), Home and End go to a level's ends, a letter to the next row it begins, Enter and Space choose, and Escape closes. Only an option without children can be chosen, unless the config's parents allows any. The popup is drawn in the browser's top layer, placed under its trigger by @foldkit/ui's anchor, over a backdrop. Focus leaving the panel closes it, by Tab or a press anywhere else; the backdrop takes that press, so it opens nothing beneath, the trigger included.

CascadeSelect.CascadeSelectView

interface · packages/mixins-ui/src/cascadeSelect.ts:455

interface CascadeSelectView<ParentMessage> {
  readonly model: Model
  readonly config: Config
  readonly onMessage: (message: Message) => NoInfer<ParentMessage>
  readonly label: string
  readonly placeholder?: string | undefined
  readonly separator?: string | undefined
  readonly layout?: 'columns' | 'drill' | undefined
  readonly expandOn?: 'click' | 'hover' | undefined
  readonly levels?: ReadonlyArray<string> | undefined
  readonly clearable?: boolean | undefined
  readonly clearLabel?: string | undefined
  readonly backLabel?: string | undefined
  readonly name?: string | undefined
  readonly disabled?: boolean | undefined
  readonly invalid?: boolean | undefined
  readonly direction?: 'ltr' | 'rtl' | undefined
  readonly anchor?: AnchorConfig | undefined
  readonly style?: NamedStyle<typeof CascadeSelectSlots> | undefined
  readonly mixins?: MixinList<ParentMessage> | undefined
}

CascadeSelect.Config

interface · packages/mixins-ui/src/cascadeSelect.ts:122

interface Config {
  readonly nodes: ReadonlyArray<Node>
  readonly parents?: boolean | undefined
  readonly loop?: boolean | undefined
  readonly closeOnSelect?: boolean | undefined
}

The options and the choices update and the view agree on.

CascadeSelect.FocusTrigger

const · packages/mixins-ui/src/cascadeSelect.ts:193

const FocusTrigger: Command.CommandDefinitionWithArgs<
  'CascadeSelect.FocusTrigger',
  { id: Schema.String },
  Effect.Effect<{ readonly _tag: 'CompletedFocusTrigger' }, never, never>
>

Focuses the trigger once the closed popup is drawn away.

CascadeSelect.init

function · packages/mixins-ui/src/cascadeSelect.ts:132

function init(options: {
  readonly id: string
  readonly value?: ReadonlyArray<string> | undefined
}): Model

CascadeSelect.labelsOf

function · packages/mixins-ui/src/cascadeSelect.ts:181

function labelsOf(
  nodes: ReadonlyArray<Node>,
  path: ReadonlyArray<string>,
): Option.None<string[]> | Option.Some<string[]>

The labels along the value, for the trigger, or none for a path that names no node.

CascadeSelect.Message

const · packages/mixins-ui/src/cascadeSelect.ts:99

const Message: MessageUnion<{
  readonly Opened: {}
  readonly Closed: {}
  readonly Pressed: { readonly key: Schema.String }
  readonly Highlighted: { readonly path: Schema.$Array<Schema.String> }
  readonly Picked: { readonly path: Schema.$Array<Schema.String> }
  readonly Entered: { readonly path: Schema.$Array<Schema.String> }
  readonly Left: {}
  readonly Cleared: {}
  readonly Placed: { readonly cramped: Schema.Boolean }
  readonly CompletedFocusTrigger: {}
  readonly CompletedRevealItem: {}
}>

CascadeSelect.Model

const · packages/mixins-ui/src/cascadeSelect.ts:86

const Model: Schema.Struct<{
  readonly id: Schema.String
  readonly open: Schema.Boolean
  readonly value: Schema.Option<Schema.$Array<Schema.String>>
  readonly highlighted: Schema.$Array<Schema.String>
  readonly cramped: Schema.Boolean
}>

CascadeSelect.Node

interface · packages/mixins-ui/src/cascadeSelect.ts:79

interface Node {
  readonly id: string
  readonly label: string
  readonly children?: ReadonlyArray<Node> | undefined
  readonly disabled?: boolean | undefined
}

An option and the options under it.

CascadeSelect.PlaceCascade

const · packages/mixins-ui/src/cascadeSelect.ts:224

const PlaceCascade: Mount.MountDefinitionWithArgs<
  'CascadeSelect.Place',
  {
    buttonId: Schema.String
    anchor: Schema.Struct<{
      readonly placement: Schema.optional<
        Schema.Literals<
          readonly [
            'top',
            'right',
            'bottom',
            'left',
            'top-start',
            'top-end',
            'right-start',
            'right-end',
            'bottom-start',
            'bottom-end',
            'left-start',
            'left-end',
          ]
        >
      >
      readonly gap: Schema.optional<Schema.Number>
      readonly offset: Schema.optional<Schema.Number>
      readonly padding: Schema.optional<
        Schema.Union<
          readonly [
            Schema.Number,
            Schema.Struct<{
              readonly top: Schema.optionalKey<Schema.Number>
              readonly right: Schema.optionalKey<Schema.Number>
              readonly bottom: Schema.optionalKey<Schema.Number>
              readonly left: Schema.optionalKey<Schema.Number>
            }>,
          ]
        >
      >
      readonly portal: Schema.optional<Schema.Boolean>
      readonly isPlacementLocked: Schema.optional<Schema.Boolean>
    }>
    levels: Schema.Number
  },
  { readonly _tag: 'Placed'; readonly cramped: boolean }
>

Shows its element in the top layer and places the panel inside it under the trigger, focusing the panel once placed: the anchor hides the panel until its first position, and focus does not land on a hidden element. It answers whether a column per level would fit the window.

CascadeSelect.RevealItem

const · packages/mixins-ui/src/cascadeSelect.ts:206

const RevealItem: Command.CommandDefinitionWithArgs<
  'CascadeSelect.RevealItem',
  { elementId: Schema.String },
  Effect.Effect<{ readonly _tag: 'CompletedRevealItem' }, never, never>
>

Scrolls the active row into its column once drawn, as the keys move it.

CascadeSelect.triggerId

function · packages/mixins-ui/src/cascadeSelect.ts:143

function triggerId(id: string): string

CascadeSelect.update

function · packages/mixins-ui/src/cascadeSelect.ts:274

function update(config: Config): (model: Model, message: Message) => Return

The cascade's update for config, the options the view draws; the same Model when nothing changes.

CascadeSelect.view

function · packages/mixins-ui/src/cascadeSelect.ts:495

function view<ParentMessage>(
  options: CascadeSelectView<ParentMessage>,
  h: HtmlBuilder<ParentMessage>,
): Html