Mixins / foldkit-mixins-ui

NestedMenu

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

export * as NestedMenu from './nestedMenu.js'

In the guides

Members

NestedMenu.init

function · packages/mixins-ui/src/nestedMenu.ts:93

function init(id: string, rows: ReadonlyArray<NestedRow>): Model

A shut menu, and a shut submenu for each row with items.

NestedMenu.Message

const · packages/mixins-ui/src/nestedMenu.ts:75

const Message: MessageUnion<{ readonly GotMenu: { readonly message: MessageUnion<{ readonly Opened: { maybeActiveItemIndex: Schema.Option<Schema.Number>; }; readonly Closed: {}; readonly BlurredItems: {}; readonly ActivatedItem: { index: Schema.Number; activationTrigger: Schema.Literals<readonly ["Pointer", "Keyboard"]>; }; readonly DeactivatedItem: {}; readonly SelectedItem: { index: Schema.Number; item: Schema.String; }; readonly RequestedItemClick: { index: Schema.Number; }; readonly Searched: { key: Schema.String; maybeTargetIndex: Schema.Option<Schema.Number>; }; readonly CompletedDelayClearSearch: { version: Schema.Number; }; readonly MovedPointerOverItem: { index: Schema.Number; screenX: Schema.Number; screenY: Schema.Number; }; readonly CompletedFocusItems: {}; readonly CompletedFocusButton: {}; readonly CompletedLockScroll: {}; readonly CompletedUnlockScroll: {}; readonly CompletedInertOthers: {}; readonly CompletedRestoreInert: {}; readonly CompletedScrollIntoView: {}; readonly CompletedClickItem: {}; readonly IgnoredMouseClick: {}; readonly SuppressedSpaceScroll: {}; readonly CompletedAnchorMenu: {}; readonly CompletedPortalMenuBackdrop: {}; readonly GotAnimationMessage: { message: import("foldkit/message").MessageUnion<{ readonly Showed: {}; readonly Hid: {}; readonly CompletedWaitForPaint: { generation: Schema.Number; }; readonly EndedAnimation: { generation: Schema.Number; }; }>; }; readonly PressedPointerOnButton: { pointerType: Schema.String; button: Schema.Number; screenX:  …;

NestedMenu.Model

const · packages/mixins-ui/src/nestedMenu.ts:63

const Model: Schema.Struct<{ readonly menu: Schema.Struct<{ readonly id: Schema.String; readonly isOpen: Schema.Boolean; readonly isAnimated: Schema.Boolean; readonly isModal: Schema.Boolean; readonly animation: Schema.Struct<{ readonly id: Schema.String; readonly isShowing: Schema.Boolean; readonly transitionState: Schema.Literals<readonly ["Idle", "EnterStart", "EnterAnimating", "LeaveStart", "LeaveAnimating"]>; readonly transitionGeneration: Schema.Number; }>; readonly maybeActiveItemIndex: Schema.Option<Schema.Number>; readonly activationTrigger: Schema.Literals<readonly ["Pointer", "Keyboard"]>; readonly searchQuery: Schema.String; readonly searchVersion: Schema.Number; readonly maybeLastPointerPosition: Schema.Option<Schema.Struct<{ readonly screenX: Schema.Number; readonly screenY: Schema.Number; }>>; readonly maybeLastButtonPointerType: Schema.Option<Schema.String>; readonly maybePointerOrigin: Schema.Option<Schema.Struct<{ readonly screenX: Schema.Number; readonly screenY: Schema.Number; readonly timeStamp: Schema.Number; }>>; }>; readonly submenus: Schema.$Array<Schema.Struct<{ readonly row: Schema.Number; readonly label: Schema.String; readonly menu: Schema.Struct<{ readonly id: Schema.String; readonly isOpen: Schema.Boolean; readonly isAnimated: Schema.Boolean; readonly isModal: Schema.Boolean; readonly animation: Schema.Struct<{ readonly id: Schema.String; readonly isShowing: Schema.Boolean; readonly transitionState: Schema.Literals<readonly ["Idle", "EnterStart", "EnterAnima …;

NestedMenu.NestedMenuSlots

const · packages/mixins-ui/src/nestedMenu.ts:40

const NestedMenuSlots: Slots.Contract<{
  readonly sub: 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 label: 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
  >
}>

A menu whose rows may open submenus, shadcn's DropdownMenuSub and the APG's: @foldkit/ui Menus, one for the menu and one per submenu, so each keeps its own keys, typeahead and rows. A submenu's row is a menuitem with aria-haspopup="menu" and aria-expanded. ArrowRight, Enter or Space on it opens the submenu with its first row active, and ArrowLeft or Escape closes it back to that row; resting the pointer on it opens it after a short delay, and resting on another row closes it. Choosing a submenu's row closes the whole menu. The Menus' Models are this module's Model, which the application keeps and folds with update, as it folds a Menu's.

Each submenu is drawn inside its row, so the submenu's own anchor places it beside the row: its button is a mark the size of the row that nothing sees, focuses or presses. Being inside the row, every key, click and release in a submenu also bubbles to the menu's panel, which answers in the Menu's Messages. update reads those against its own Model: while a submenu is open, focus is in it, so the menu's key Messages and its blur are the submenu's and are dropped, and the menu's Closed is the submenu's Escape. The submenu's own Closed is dropped for that reason, and it draws no click-away: the menu's serves both, closing everything on pointerdown. The keys the Menu does not answer (ArrowRight, ArrowLeft, and Tab while a submenu is open) are heard on the menu's panel, outside the submenus.

One level deep. Pass Menu.fluid to the submenus only: the menu's rows hold its submenus' rows, which its highlight would chase too.

NestedMenu.NestedMenuView

interface · packages/mixins-ui/src/nestedMenu.ts:361

interface NestedMenuView<ParentMessage> {
  readonly model: Model
  readonly rows: ReadonlyArray<NestedRow>
  readonly buttonContent: Html
  readonly isRowDisabled?: ((label: string) => boolean) | undefined
  readonly onMessage: (message: Message) => NoInfer<ParentMessage>
  readonly style?: NamedStyle<typeof NestedMenuSlots> | undefined
  readonly menuMixins: MixinList<ParentMessage>
  readonly submenuMixins: MixinList<ParentMessage>
}

NestedMenu.NestedRow

interface · packages/mixins-ui/src/nestedMenu.ts:49

interface NestedRow {
  readonly label: string
  readonly items?: ReadonlyArray<string> | undefined
  readonly isItemDisabled?: ((item: string) => boolean) | undefined
}

One row of the menu; a row with items opens a submenu of them.

NestedMenu.OutMessage

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

const OutMessage: MessageUnion<{
  readonly Selected: {
    readonly value: Schema.String
    readonly submenu: Schema.Option<Schema.String>
  }
}>

NestedMenu.update

function · packages/mixins-ui/src/nestedMenu.ts:300

function update(model: Model, message: Message): Return

The menu after message; the same Model when nothing changes.

NestedMenu.view

function · packages/mixins-ui/src/nestedMenu.ts:391

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