Primitives / foldkit-primitives/interaction

FocusScope

namespace · packages/primitives/src/interaction/focus-scope.ts:5

export * as FocusScope from './focus-scope.js'

FocusScope as a Behavior: attaches the foldkit-primitives/dom Mount to a container slot. No Bundle, because which element has focus is a DOM fact.

Members

FocusScope.behavior

function · packages/primitives/src/interaction/focus-scope.ts:27

function behavior<Slots>(
  slots: Slots,
): <Input, ParentMessage>(
  options: BehaviorOptions<Slots>,
) => Behavior.NamedBehavior<Slots, Input, ParentMessage>

On insert the container focuses initialFocus, else its first tabbable descendant, else itself; with contain, Tab and Shift+Tab wrap inside and focus that lands outside comes back to the first tabbable; on unmount, with restore, focus returns to the element that had it. A dialog that is not a native <dialog>, a menu, or a command palette takes this.

FocusScope.BehaviorOptions

interface · packages/primitives/src/interaction/focus-scope.ts:10

interface BehaviorOptions<Slots> {
  readonly container: keyof Slots & string
  readonly contain?: boolean
  readonly restore?: boolean
  readonly initialFocus?: string
}

FocusScope.mount

const · packages/primitives/src/dom/focus-scope.ts:39

const mount: Mount.MountDefinitionWithArgs<
  'FocusScope',
  {
    contain: Schema.Boolean
    restore: Schema.Boolean
    initialFocus: Schema.NullOr<Schema.String>
  },
  never
>

A focus scope as a Mount: on insert it remembers what was focused, focuses an initial element inside, and with contain keeps Tab and Shift+Tab cycling inside; on unmount it gives focus back. Nothing crosses to the Model: which element had focus is a DOM fact, so this is element lifecycle, not state. It emits no Messages.

FocusScope.tabbableWithin

function · packages/primitives/src/dom/focus-scope.ts:20

function tabbableWithin(root: Element): ReadonlyArray<HTMLElement>

Tabbable descendants in DOM order: focusable, visible, and not tabindex="-1".