foldkit-primitives 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +358 -37
- package/dist/device/index.d.mts +2 -2
- package/dist/dom/index.d.mts +2 -0
- package/dist/dom/index.d.mts.map +1 -1
- package/dist/dom/index.mjs +2 -1
- package/dist/dom/index.mjs.map +1 -1
- package/dist/events/index.d.mts +2 -0
- package/dist/events/index.d.mts.map +1 -1
- package/dist/events/index.mjs +2 -1
- package/dist/events/index.mjs.map +1 -1
- package/dist/index-Cppf-lBx.d.mts +130 -0
- package/dist/index-Cppf-lBx.d.mts.map +1 -0
- package/dist/index.d.mts +4 -2
- package/dist/index.mjs +4 -2
- package/dist/input-modality-DGOhAknO.d.mts +26 -0
- package/dist/input-modality-DGOhAknO.d.mts.map +1 -0
- package/dist/input-modality-m6v7__Xs.mjs +43 -0
- package/dist/input-modality-m6v7__Xs.mjs.map +1 -0
- package/dist/interaction/index.d.mts +862 -0
- package/dist/interaction/index.d.mts.map +1 -0
- package/dist/interaction/index.mjs +1364 -0
- package/dist/interaction/index.mjs.map +1 -0
- package/dist/motion/index.d.mts +2 -112
- package/dist/motion/index.mjs +2 -208
- package/dist/motion-LfJYAMnP.mjs +239 -0
- package/dist/motion-LfJYAMnP.mjs.map +1 -0
- package/dist/move-CZwHyTGk.mjs +215 -0
- package/dist/move-CZwHyTGk.mjs.map +1 -0
- package/dist/move-DNiMPcuf.d.mts +58 -0
- package/dist/move-DNiMPcuf.d.mts.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +20 -8
- package/dist/motion/index.d.mts.map +0 -1
- package/dist/motion/index.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["Layer","Model","Message","bundle","behavior","behavior","FocusScopeMount","behavior","Model","Message","Args","bundle","move","behavior","Model","Message","Args","bundle","behavior","RovingTabindex.tabStop","RovingTabindex.idSelector","RovingTabindex.itemAttributes","ScrollLockMount","HideOutsideMount","Model","Message","Args","bundle","Model","Message","Args","bundle","behavior","Model","Message","Args","RovingTabindex.Orientation","bundle","RovingTabindex.move","Typeahead.isPrintable","Typeahead.match","behavior","RovingTabindex.tabStop","RovingTabindex.idSelector","RovingTabindex.itemAttributes","Model","Message","Args","isActivationKey","bundle","behavior","Model","Message","Press.PointerType","Args","bundle","behavior","Press.events","behavior","MoveMount"],"sources":["../../src/interaction/dismiss-layer.ts","../../src/interaction/focus-scope.ts","../../src/interaction/focus-visible.ts","../../src/interaction/roving-tabindex.ts","../../src/interaction/grid-navigation.ts","../../src/interaction/layers.ts","../../src/interaction/live-announce.ts","../../src/interaction/typeahead.ts","../../src/interaction/list-navigation.ts","../../src/interaction/press.ts","../../src/interaction/long-press.ts","../../src/interaction/move.ts","../../src/interaction/selection.ts"],"sourcesContent":["/**\n * Dismissable layers: Escape closes the topmost open layer, a pointer press\n * outside closes the layers it is outside of. One Bundle, placed once, owns\n * the document listeners; each layer's Behavior marks its container (and its\n * trigger) with data attributes. The stack is the DOM order of the marked\n * elements at the moment of the event, so a layer opening or closing needs no\n * registration. A press inside a parent layer is outside its children, so the\n * parent stays and the children go; a press on a layer's trigger counts as\n * inside it, so a trigger never dismisses and reopens in one click. What a\n * dismissal does is the placement's `onOut`.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport type { HtmlBuilder } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Subscription from 'foldkit/subscription'\nimport type * as Update from 'foldkit/update'\nimport { Bundle, type Declared } from 'foldkit-bundle'\nimport { Behavior, Capability } from 'foldkit-mixins'\n\nexport const LAYER_ATTRIBUTE = 'data-foldkit-plus-layer'\nexport const TRIGGER_ATTRIBUTE = 'data-foldkit-plus-layer-trigger'\nconst OUTSIDE_ATTRIBUTE = 'data-foldkit-plus-layer-outside'\nconst ESCAPE_ATTRIBUTE = 'data-foldkit-plus-layer-escape'\n\n/** An open layer as the document showed it: bottom to top is DOM order. */\nexport const Layer = Schema.Struct({\n id: Schema.String,\n /** Dismiss on a press outside it. */\n outside: Schema.Boolean,\n /** Dismiss on Escape when topmost. */\n escape: Schema.Boolean,\n})\nexport type Layer = typeof Layer.Type\n\nexport const Model = Schema.Struct({\n /** The open layers at the last event, bottom to top. */\n layers: Schema.Array(Layer),\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n /** A pointer went down; `inside` are the layers whose element or trigger was on the event's path. */\n PressedAt: { layers: Schema.Array(Layer), inside: Schema.Array(Schema.String) },\n PressedEscape: { layers: Schema.Array(Layer) },\n})\nexport type Message = typeof Message.Type\n\nexport const Dismiss = Schema.TaggedStruct('Dismiss', { ids: Schema.Array(Schema.String) })\nexport type Dismiss = typeof Dismiss.Type\n\n/** Which layers a press inside `inside` dismisses: every layer above the topmost one hit, or all when none was. */\nexport const toDismiss = (\n layers: ReadonlyArray<Layer>,\n inside: ReadonlyArray<string>,\n): ReadonlyArray<string> => {\n let topmostHit = -1\n layers.forEach((layer, index) => {\n if (inside.includes(layer.id)) topmostHit = index\n })\n return layers\n .slice(topmostHit + 1)\n .filter(layer => layer.outside)\n .map(layer => layer.id)\n}\n\ntype Return = Update.ReturnWithOutMessage<Model, Message, Dismiss>\n\nexport const bundle = Bundle.make('DismissLayer', {\n Model,\n Message,\n init: () => ({ model: { layers: [] } }),\n update: (_model, message): Return =>\n Message.match<Return>(message, {\n PressedAt: ({ layers, inside }) => {\n const ids = toDismiss(layers, inside)\n return ids.length === 0\n ? { model: { layers } }\n : { model: { layers }, outMessage: Dismiss.make({ ids }) }\n },\n PressedEscape: ({ layers }) => {\n const top = layers[layers.length - 1]\n return top !== undefined && top.escape\n ? { model: { layers }, outMessage: Dismiss.make({ ids: [top.id] }) }\n : { model: { layers } }\n },\n }),\n subscriptions: () =>\n Subscription.make<Model, Message>()(() => ({\n document: Subscription.persistent(documentEvents()),\n })),\n})\n\nconst openLayers = (root: ParentNode): ReadonlyArray<Layer> =>\n Array.from(root.querySelectorAll(`[${LAYER_ATTRIBUTE}]`)).map(element => ({\n id: element.getAttribute(LAYER_ATTRIBUTE) ?? '',\n outside: element.getAttribute(OUTSIDE_ATTRIBUTE) !== 'false',\n escape: element.getAttribute(ESCAPE_ATTRIBUTE) !== 'false',\n }))\n\n/** The layer ids whose element or trigger is on the event's composed path. */\nexport const layersOnPath = (path: ReadonlyArray<EventTarget>): ReadonlyArray<string> => {\n const ids: Array<string> = []\n for (const target of path) {\n if (!(target instanceof Element)) continue\n const id = target.getAttribute(LAYER_ATTRIBUTE) ?? target.getAttribute(TRIGGER_ATTRIBUTE)\n if (id !== null && !ids.includes(id)) ids.push(id)\n }\n return ids\n}\n\n/**\n * The facts are read inside the listener, not after: `composedPath()` is\n * empty once dispatch has finished, and the open layers must be the ones the\n * press saw, not the ones a later render left.\n */\nconst documentEvents = (): Stream.Stream<Message> => {\n if (typeof document === 'undefined') return Stream.empty\n return Stream.callback<Message>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n const onPointerDown = (event: Event) =>\n Queue.offerUnsafe(\n queue,\n Message.PressedAt({\n layers: openLayers(document),\n inside: layersOnPath(event.composedPath()),\n }),\n )\n const onKeyDown = (event: Event) => {\n if ((event as KeyboardEvent).key !== 'Escape') return\n Queue.offerUnsafe(queue, Message.PressedEscape({ layers: openLayers(document) }))\n }\n document.addEventListener('pointerdown', onPointerDown, { capture: true })\n document.addEventListener('keydown', onKeyDown, { capture: true })\n return { onPointerDown, onKeyDown }\n }),\n ({ onPointerDown, onKeyDown }) =>\n Effect.sync(() => {\n document.removeEventListener('pointerdown', onPointerDown, { capture: true })\n document.removeEventListener('keydown', onKeyDown, { capture: true })\n }),\n ),\n )\n}\n\nexport interface BehaviorOptions<Input, Slots> {\n /** The layer's root, rendered only while the layer is open. */\n readonly layer: keyof Slots & string\n /** The element that opens the layer, so a press on it is inside. */\n readonly trigger?: keyof Slots & string\n readonly id: (input: Input) => string\n /** Dismiss on a press outside. Default `true`. */\n readonly outsidePress?: boolean\n /** Dismiss on Escape when topmost. Default `true`. */\n readonly escape?: boolean\n}\n\n/**\n * Marks the layer's container and trigger for the placed stack. The layer\n * takes part only while its element is rendered, so an `open` flag in the\n * parent Model is the whole lifecycle. Not for an element with `popover`,\n * which the browser dismisses itself.\n */\nexport const behavior =\n <Field extends string>(_declared: Declared<typeof bundle, Field>) =>\n <Slots>(slots: Slots) =>\n <Input, ParentMessage>(\n options: BehaviorOptions<Input, Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.layer]: Behavior.slot({\n requires: { capability: Capability.Container },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) => [\n h.Attribute(LAYER_ATTRIBUTE, options.id(input)),\n ...(options.outsidePress === false ? [h.Attribute(OUTSIDE_ATTRIBUTE, 'false')] : []),\n ...(options.escape === false ? [h.Attribute(ESCAPE_ATTRIBUTE, 'false')] : []),\n ],\n }),\n ...(options.trigger === undefined\n ? {}\n : {\n [options.trigger]: Behavior.slot({\n requires: { capability: Capability.Interactive },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) => [h.Attribute(TRIGGER_ATTRIBUTE, options.id(input))],\n }),\n }),\n // Keyed by values the caller chose; `forSlots` checks the keys exist.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'DismissLayer' },\n )\n","/**\n * `FocusScope` as a Behavior: attaches the `foldkit-primitives/dom` Mount to a\n * container slot. No Bundle, because which element has focus is a DOM fact.\n */\nimport { Behavior, Capability } from 'foldkit-mixins'\nimport { FocusScope as FocusScopeMount } from '../dom/focus-scope.js'\n\nexport { FocusScope as mount, tabbableWithin } from '../dom/focus-scope.js'\n\nexport interface BehaviorOptions<Slots> {\n readonly container: keyof Slots & string\n /** Keep Tab, Shift+Tab, and a stray focus inside. Default `true`. */\n readonly contain?: boolean\n /** Give focus back to what had it when the container unmounts. Default `true`. */\n readonly restore?: boolean\n /** A selector inside the container to focus first. */\n readonly initialFocus?: string\n}\n\n/**\n * On insert the container focuses `initialFocus`, else its first tabbable\n * descendant, else itself; with `contain`, Tab and Shift+Tab wrap inside and\n * focus that lands outside comes back to the first tabbable; on unmount, with\n * `restore`, focus returns to the element that had it. A dialog that is not\n * a native `<dialog>`, a menu, or a command palette takes this.\n */\nexport const behavior =\n <Slots>(slots: Slots) =>\n <Input, ParentMessage>(\n options: BehaviorOptions<Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.container]: Behavior.slot({\n requires: { capability: Capability.Container },\n mount: () =>\n FocusScopeMount({\n contain: options.contain ?? true,\n restore: options.restore ?? true,\n initialFocus: options.initialFocus ?? null,\n }),\n }),\n // Keyed by a value the caller chose; `forSlots` checks the key exists.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'FocusScope' },\n )\n","/**\n * `FocusVisible` as a Behavior over a placed `InputModality`: the target gets\n * `data-focus-visible` while the page is being driven by keyboard, so a\n * stylesheet can show a ring with `[data-focus-visible]:focus` and hide it\n * for pointer users. CSS `:focus-visible` does this without any of it; this\n * is for a design system that must decide in the Model.\n */\nimport type { HtmlBuilder } from 'foldkit/html'\nimport type { Declared } from 'foldkit-bundle'\nimport { Behavior, Capability } from 'foldkit-mixins'\nimport { InputModality, type InputModalityModel } from '../events/input-modality.js'\n\nexport { InputModality as bundle, Modality, modalityOfKey } from '../events/input-modality.js'\nexport type { InputModalityModel as Model } from '../events/input-modality.js'\n\nexport interface BehaviorOptions<Slots> {\n readonly target: keyof Slots & string\n}\n\nexport const behavior =\n <Field extends string>(declared: Declared<typeof InputModality, Field>) =>\n <Slots>(slots: Slots) =>\n <Input extends { readonly [K in Field]: InputModalityModel }, ParentMessage>(\n options: BehaviorOptions<Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.target]: Behavior.slot({\n requires: { capability: Capability.Focusable },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) =>\n input[declared.field].modality === 'keyboard'\n ? [h.DataAttribute('focus-visible', 'true')]\n : [],\n }),\n // Keyed by a value the caller chose; `forSlots` checks the key exists.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'FocusVisible' },\n )\n","/**\n * One tab stop for a set of items (a toolbar, a tab list, a listbox): arrows\n * move focus between them, the rest stay out of the tab order. The current\n * item's id is the Model slice, so a reorder keeps the same item current and a\n * resumed page knows where focus was. Which item comes next is a pure\n * function of the items and the key; the Behavior wires it to the slots.\n */\nimport { Option, Schema } from 'effect'\nimport type { Attribute, HtmlBuilder, KeyboardModifiers } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle } from 'foldkit-bundle'\nimport { Behavior, Behaviors, Capability, type SlotItem } from 'foldkit-mixins'\nimport type { Declared } from 'foldkit-bundle'\n\nexport const Orientation = Schema.Literals(['vertical', 'horizontal', 'both'])\nexport type Orientation = typeof Orientation.Type\n\nexport const Model = Schema.Struct({\n /** The current item's id, or `null` before any item has been focused. */\n current: Schema.NullOr(Schema.String),\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n /** An item became current: the user focused it, or a key moved there. */\n Focused: { id: Schema.String },\n})\nexport type Message = typeof Message.Type\n\nexport const Args = Schema.Struct({\n orientation: Orientation,\n /** Wrap from the last enabled item to the first, and back. */\n loop: Schema.Boolean,\n /** Keep DOM focus on the container and point at the current item with\n * `aria-activedescendant` instead of moving focus. */\n virtual: Schema.Boolean,\n})\nexport type Args = typeof Args.Type\n\nexport const bundle = Bundle.make('RovingTabindex', {\n Model,\n Message,\n args: Args,\n init: () => ({ model: { current: null } }),\n update: (_model, message) => ({ model: { current: message.id } }),\n})\n\nexport type Direction = 'ltr' | 'rtl'\n\nexport interface MoveOptions {\n readonly orientation: Orientation\n readonly loop: boolean\n readonly direction: Direction\n /** How many enabled items PageUp and PageDown move; absent, they are not handled. */\n readonly page?: number\n}\n\nconst isPlain = (modifiers: KeyboardModifiers): boolean =>\n !modifiers.ctrlKey && !modifiers.altKey && !modifiers.metaKey\n\n/**\n * The index the key moves to from `current`, over the enabled indices only, or\n * `undefined` when the key is not a navigation key here. `current` may be\n * `-1` (nothing current yet): arrows then land on the first or last enabled\n * item. Under `rtl`, left and right swap. PageUp and PageDown move by `page`\n * and clamp at the ends, never wrapping.\n */\nexport const move = (\n enabled: ReadonlyArray<number>,\n current: number,\n key: string,\n modifiers: KeyboardModifiers,\n options: MoveOptions,\n): number | undefined => {\n if (enabled.length === 0 || !isPlain(modifiers)) return undefined\n const first = enabled[0]!\n const last = enabled[enabled.length - 1]!\n if (key === 'Home') return first\n if (key === 'End') return last\n if (options.page !== undefined && (key === 'PageUp' || key === 'PageDown')) {\n const position = enabled.indexOf(current)\n if (position === -1) return key === 'PageDown' ? first : last\n const jumped = position + (key === 'PageDown' ? options.page : -options.page)\n return enabled[Math.min(enabled.length - 1, Math.max(0, jumped))]\n }\n const vertical = options.orientation !== 'horizontal'\n const horizontal = options.orientation !== 'vertical'\n const forwardKey = options.direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight'\n const backwardKey = options.direction === 'rtl' ? 'ArrowRight' : 'ArrowLeft'\n const step =\n (vertical && key === 'ArrowDown') || (horizontal && key === forwardKey)\n ? 1\n : (vertical && key === 'ArrowUp') || (horizontal && key === backwardKey)\n ? -1\n : 0\n if (step === 0) return undefined\n const position = enabled.indexOf(current)\n if (position === -1) return step === 1 ? first : last\n const next = position + step\n if (next < 0) return options.loop ? last : first\n if (next >= enabled.length) return options.loop ? first : last\n return enabled[next]\n}\n\n/** The item that holds the tab stop: the current one, else the first enabled. */\nexport const tabStop = (\n items: Behaviors.Collection.Items<unknown>,\n current: string | null,\n): number => {\n const index = current === null ? -1 : items.indexOf(current)\n if (index !== -1 && !items.isDisabled(index)) return index\n return items.enabled[0] ?? -1\n}\n\nexport interface BehaviorOptions<Input, Slots> {\n /** The slot that receives the arrow keys. */\n readonly container: keyof Slots & string\n /** The slot rendered once per item; must carry the item's id (see `Behaviors.Collection`). */\n readonly item: keyof Slots & string\n readonly items: (input: Input) => Behaviors.Collection.Items<unknown>\n /** Default `'ltr'`; return `'rtl'` to swap left and right. */\n readonly direction?: (input: Input) => Direction\n}\n\n/** A selector for an element by id, safe for any id. */\nexport const idSelector = (id: string): string => `[id=\"${id.replace(/[\"\\\\]/g, '\\\\$&')}\"]`\n\n/**\n * The attributes one item gets: `tabindex` 0 when it holds the tab stop and\n * -1 otherwise (none under `virtual`), and `OnFocus` reporting it current.\n * Shared with `ListNavigation`.\n */\nexport const itemAttributes = <ParentMessage>(\n h: HtmlBuilder<ParentMessage>,\n items: Behaviors.Collection.Items<unknown>,\n current: string | null,\n item: SlotItem,\n virtual: boolean,\n focused: (id: string) => ParentMessage,\n): ReadonlyArray<Attribute<ParentMessage>> => {\n const id = item.id ?? items.ids[item.index]\n if (id === undefined) return []\n const isStop = tabStop(items, current) === item.index\n return [...(virtual ? [] : [h.Tabindex(isStop ? 0 : -1)]), h.OnFocus(focused(id))]\n}\n\n/**\n * Wires a placed `RovingTabindex` to the slots: the container's arrow, Home\n * and End keys focus the next item (synchronously, then dispatch), each item\n * gets `tabindex` 0 or -1 and reports `Focused` when focused. Under `virtual`\n * DOM focus never leaves the container: the key only dispatches, the\n * container carries `aria-activedescendant`, and items keep no `tabindex`.\n *\n * Every handled key is default-prevented. Nothing is written on dispose: the\n * attributes are data, so a view that no longer attaches this leaves no\n * `tabindex` behind.\n */\nexport const behavior =\n <Field extends string>(declared: Declared<typeof bundle, Field>, args: Args) =>\n <Slots>(slots: Slots) =>\n <Input extends { readonly [K in Field]: Model }, ParentMessage>(\n options: BehaviorOptions<Input, Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> => {\n const wrap = (id: string): ParentMessage =>\n declared.wrapper.make(Message.Focused({ id })) as unknown as ParentMessage\n const slice = (input: Input): Model => input[declared.field]\n return Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.container]: Behavior.slot({\n requires: { capability: Capability.Interactive },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) => {\n const items = options.items(input)\n const current = slice(input).current\n const from = current === null ? -1 : items.indexOf(current)\n const direction = options.direction?.(input) ?? 'ltr'\n const stop = tabStop(items, current)\n const stopId = stop === -1 ? undefined : items.ids[stop]\n const target = (key: string, modifiers: KeyboardModifiers): string | undefined => {\n const next = move(items.enabled, from, key, modifiers, { ...args, direction })\n return next === undefined ? undefined : items.ids[next]\n }\n // Virtual: DOM focus stays on the container, only the pointer moves.\n if (args.virtual) {\n return [\n h.OnKeyDownPreventDefault((key, modifiers) =>\n Option.map(Option.fromNullishOr(target(key, modifiers)), wrap),\n ),\n ...(stopId === undefined ? [] : [h.AriaActiveDescendant(stopId)]),\n ]\n }\n return [\n h.OnKeyDownFocus((key, modifiers) =>\n Option.map(Option.fromNullishOr(target(key, modifiers)), id => ({\n focusSelector: idSelector(id),\n message: wrap(id),\n })),\n ),\n ]\n },\n }),\n [options.item]: Behavior.slot({\n requires: { capability: Capability.Focusable },\n attributes: ({\n input,\n h,\n item,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n readonly item?: SlotItem\n }) => {\n if (item === undefined) return []\n return itemAttributes(\n h,\n options.items(input),\n slice(input).current,\n item,\n args.virtual,\n wrap,\n )\n },\n }),\n // Keyed by values the caller chose; `forSlots` checks both keys exist.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'RovingTabindex' },\n )\n }\n","/**\n * Two-dimensional arrow keys over a set of items laid out in rows of\n * `columns` (a calendar grid, a color swatch picker, an emoji palette). The\n * Model slice is the current item's id, as with `RovingTabindex`, so a\n * reorder keeps the same item current. Which cell a key lands on is the pure\n * `move`; the Behavior wires it to a container slot and a per-item slot and\n * shares `RovingTabindex`'s item attributes, so the two are interchangeable\n * on a view.\n */\nimport { Option, Schema } from 'effect'\nimport type { HtmlBuilder, KeyboardModifiers } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle, type Declared } from 'foldkit-bundle'\nimport { Behavior, Behaviors, Capability, type SlotItem } from 'foldkit-mixins'\nimport * as RovingTabindex from './roving-tabindex.js'\n\nexport const Model = Schema.Struct({\n /** The current cell's id, or `null` before any cell has been focused. */\n current: Schema.NullOr(Schema.String),\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n /** A cell became current: the user focused it, or a key moved there. */\n Focused: { id: Schema.String },\n})\nexport type Message = typeof Message.Type\n\nexport const Args = Schema.Struct({\n /** Cells per row; the last row may be short. */\n columns: Schema.Number.pipe(Schema.check(Schema.isGreaterThanOrEqualTo(1))),\n /** Left and right continue into the previous and next row; up and down\n * continue into the previous and next column. Off, they stop at the edge. */\n wrap: Schema.Boolean,\n /** Keep DOM focus on the container and point at the current cell with\n * `aria-activedescendant` instead of moving focus. */\n virtual: Schema.Boolean,\n})\nexport type Args = typeof Args.Type\n\nexport const bundle = Bundle.make('GridNavigation', {\n Model,\n Message,\n args: Args,\n init: () => ({ model: { current: null } }),\n update: (_model, message) => ({ model: { current: message.id } }),\n})\n\nexport interface MoveOptions {\n readonly columns: number\n readonly wrap: boolean\n readonly direction: RovingTabindex.Direction\n}\n\n/**\n * The index the key moves to from `current` over a grid of `count` cells in\n * rows of `columns`, skipping cells not in `enabled`, or `undefined` when the\n * key is not a grid key. Left and right step within the row, and across rows\n * under `wrap`; up and down step within the column, and across columns under\n * `wrap`. Home and End are the row's first and last enabled cell; with Ctrl\n * they are the grid's. At an edge without `wrap` the key is consumed and\n * `current` is returned. From `-1` (nothing current) a forward key lands on\n * the first enabled cell and a backward key on the last. Under `rtl`, left\n * and right swap.\n */\nexport const move = (\n enabled: ReadonlyArray<number>,\n count: number,\n current: number,\n key: string,\n modifiers: KeyboardModifiers,\n options: MoveOptions,\n): number | undefined => {\n if (enabled.length === 0 || modifiers.altKey || modifiers.metaKey) return undefined\n const first = enabled[0]!\n const last = enabled[enabled.length - 1]!\n const isEnabled = (index: number) => enabled.includes(index)\n const { columns } = options\n const row = (index: number) => Math.floor(index / columns)\n\n if (key === 'Home' || key === 'End') {\n if (modifiers.ctrlKey || current === -1) return key === 'Home' ? first : last\n const inRow = enabled.filter(index => row(index) === row(current))\n return key === 'Home' ? (inRow[0] ?? current) : (inRow[inRow.length - 1] ?? current)\n }\n if (modifiers.ctrlKey) return undefined\n\n const forwardKey = options.direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight'\n const backwardKey = options.direction === 'rtl' ? 'ArrowRight' : 'ArrowLeft'\n const axis: 'row' | 'column' | undefined =\n key === forwardKey || key === backwardKey\n ? 'row'\n : key === 'ArrowDown' || key === 'ArrowUp'\n ? 'column'\n : undefined\n if (axis === undefined) return undefined\n const step = key === forwardKey || key === 'ArrowDown' ? 1 : -1\n if (current === -1) return step === 1 ? first : last\n\n if (axis === 'row') {\n // Reading order; without wrap the walk stops at the row's edge.\n for (let index = current + step; index >= 0 && index < count; index += step) {\n if (!options.wrap && row(index) !== row(current)) return current\n if (isEnabled(index)) return index\n }\n return current\n }\n // Column-major; without wrap the walk stops at the column's edge.\n const column = current % columns\n for (let index = current + step * columns; ; index += step * columns) {\n if (index < 0 || index >= count) {\n if (!options.wrap) return current\n const nextColumn = column + step\n if (nextColumn < 0 || nextColumn >= columns) return current\n // Continue from the top (or bottom) of the neighbouring column.\n const start =\n step === 1\n ? nextColumn\n : nextColumn + columns * Math.floor((count - 1 - nextColumn) / columns)\n for (let next = start; next >= 0 && next < count; next += step * columns) {\n if (isEnabled(next)) return next\n }\n return current\n }\n if (isEnabled(index)) return index\n }\n}\n\nexport interface BehaviorOptions<Input, Slots> {\n /** The slot that receives the arrow keys. */\n readonly container: keyof Slots & string\n /** The slot rendered once per cell; must carry the cell's id (see `Behaviors.Collection`). */\n readonly item: keyof Slots & string\n readonly items: (input: Input) => Behaviors.Collection.Items<unknown>\n /** Default `'ltr'`; return `'rtl'` to swap left and right. */\n readonly direction?: (input: Input) => RovingTabindex.Direction\n}\n\n/**\n * Wires a placed `GridNavigation` to the slots exactly as `RovingTabindex`\n * does, with `move` above deciding the cell: the container's arrows, Home and\n * End focus the next cell (synchronously, then dispatch) or, under `virtual`,\n * only dispatch and point with `aria-activedescendant`; each cell gets\n * `tabindex` 0 or -1 and reports `Focused` when focused.\n */\nexport const behavior =\n <Field extends string>(declared: Declared<typeof bundle, Field>, args: Args) =>\n <Slots>(slots: Slots) =>\n <Input extends { readonly [K in Field]: Model }, ParentMessage>(\n options: BehaviorOptions<Input, Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> => {\n const wrap = (id: string): ParentMessage =>\n declared.wrapper.make(Message.Focused({ id })) as unknown as ParentMessage\n const slice = (input: Input): Model => input[declared.field]\n return Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.container]: Behavior.slot({\n requires: { capability: Capability.Interactive },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) => {\n const items = options.items(input)\n const current = slice(input).current\n const from = current === null ? -1 : items.indexOf(current)\n const direction = options.direction?.(input) ?? 'ltr'\n const stop = RovingTabindex.tabStop(items, current)\n const stopId = stop === -1 ? undefined : items.ids[stop]\n const target = (key: string, modifiers: KeyboardModifiers): string | undefined => {\n const next = move(items.enabled, items.size, from, key, modifiers, {\n columns: args.columns,\n wrap: args.wrap,\n direction,\n })\n return next === undefined ? undefined : items.ids[next]\n }\n if (args.virtual) {\n return [\n h.OnKeyDownPreventDefault((key, modifiers) =>\n Option.map(Option.fromNullishOr(target(key, modifiers)), wrap),\n ),\n ...(stopId === undefined ? [] : [h.AriaActiveDescendant(stopId)]),\n ]\n }\n return [\n h.OnKeyDownFocus((key, modifiers) =>\n Option.map(Option.fromNullishOr(target(key, modifiers)), id => ({\n focusSelector: RovingTabindex.idSelector(id),\n message: wrap(id),\n })),\n ),\n ]\n },\n }),\n [options.item]: Behavior.slot({\n requires: { capability: Capability.Focusable },\n attributes: ({\n input,\n h,\n item,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n readonly item?: SlotItem\n }) => {\n if (item === undefined) return []\n return RovingTabindex.itemAttributes(\n h,\n options.items(input),\n slice(input).current,\n item,\n args.virtual,\n wrap,\n )\n },\n }),\n // Keyed by values the caller chose; `forSlots` checks both keys exist.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'GridNavigation' },\n )\n }\n","/**\n * `ScrollLock` and `HideOutside` as Behaviors: each attaches its\n * `foldkit-primitives/dom` Mount to a container slot. No Bundle, because the\n * lock and the inert set are document facts Foldkit already refcounts and\n * keys.\n */\nimport type { MountAction } from 'foldkit/mount'\nimport { Behavior, Capability } from 'foldkit-mixins'\nimport { HideOutside as HideOutsideMount, ScrollLock as ScrollLockMount } from '../dom/layers.js'\n\nexport { HideOutside as hideOutsideMount, ScrollLock as scrollLockMount } from '../dom/layers.js'\n\nconst mountOn =\n (name: string, mount: () => MountAction<never>) =>\n <Slots>(slots: Slots) =>\n <Input, ParentMessage>(options: {\n readonly container: keyof Slots & string\n }): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.container]: Behavior.slot({\n requires: { capability: Capability.Container },\n mount,\n }),\n // Keyed by a value the caller chose; `forSlots` checks the key exists.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name },\n )\n\n/** Locks the document's scroll while the container is mounted; nested locks release together. */\nexport const scrollLock = mountOn('ScrollLock', () => ScrollLockMount())\n\n/** Marks everything outside the container inert while it is mounted. */\nexport const hideOutside = mountOn('HideOutside', () => HideOutsideMount())\n","/**\n * Announcements for assistive technology: one Bundle, placed once, holds the\n * text of a polite and an assertive live region. An announcement waits\n * `debounceMs` so a burst reads once, then clears after `clearAfterMs`, both\n * on Effect's clock with a generation so a superseded timer changes nothing.\n * The same text twice gets a trailing no-break space toggled, which is what\n * makes a screen reader read it again. `view` renders the two regions; put it\n * once in the page and style them visually hidden.\n */\nimport { Effect, Schema } from 'effect'\nimport type { Html, HtmlBuilder } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type * as Update from 'foldkit/update'\nimport { Bundle, type Declared } from 'foldkit-bundle'\n\nexport const Politeness = Schema.Literals(['polite', 'assertive'])\nexport type Politeness = typeof Politeness.Type\n\nexport const Model = Schema.Struct({\n polite: Schema.String,\n assertive: Schema.String,\n /** Waiting for the debounce to read. */\n pending: Schema.NullOr(Schema.Struct({ message: Schema.String, politeness: Politeness })),\n generation: Schema.Number,\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n Announced: { message: Schema.String, politeness: Politeness },\n Read: { generation: Schema.Number },\n Cleared: { generation: Schema.Number },\n})\nexport type Message = typeof Message.Type\n\nconst positive = Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isFinite()),\n)\n\nexport const Args = Schema.Struct({\n /** A burst of announcements inside this window reads once, the last one. */\n debounceMs: positive,\n /** How long the text stays in the region. */\n clearAfterMs: positive,\n})\nexport type Args = typeof Args.Type\n\nconst NBSP = ' '\n\n/** The text to put in the region so that `next` is read even when it equals `current`. */\nexport const reread = (current: string, next: string): string =>\n current === next ? `${next}${NBSP}` : current === `${next}${NBSP}` ? next : next\n\ntype Return = Update.Return<Model, Message>\n\nconst after = (ms: number, message: Message, name: string, generation: number) => ({\n name,\n args: { generation },\n effect: Effect.as(Effect.sleep(ms), message),\n})\n\nexport const bundle = Bundle.make('LiveAnnounce', {\n Model,\n Message,\n args: Args,\n init: () => ({ model: { polite: '', assertive: '', pending: null, generation: 0 } }),\n update: (model, message, args): Return =>\n Message.match<Return>(message, {\n Announced: ({ message: text, politeness }) => {\n const generation = model.generation + 1\n return {\n model: { ...model, pending: { message: text, politeness }, generation },\n commands: [\n after(args.debounceMs, Message.Read({ generation }), 'LiveAnnounce.read', generation),\n ],\n }\n },\n Read: ({ generation }) => {\n if (generation !== model.generation || model.pending === null) return { model }\n const { message: text, politeness } = model.pending\n const region = politeness === 'polite' ? 'polite' : 'assertive'\n return {\n model: { ...model, pending: null, [region]: reread(model[region], text) },\n commands: [\n after(\n args.clearAfterMs,\n Message.Cleared({ generation }),\n 'LiveAnnounce.clear',\n generation,\n ),\n ],\n }\n },\n Cleared: ({ generation }) =>\n generation === model.generation\n ? { model: { ...model, polite: '', assertive: '' } }\n : { model },\n }),\n})\n\n/** The parent Message that announces `text`, to return from `update` or `onOut`. */\nexport const say =\n <Field extends string>(declared: Declared<typeof bundle, Field>) =>\n <ParentMessage>(text: string, politeness: Politeness = 'polite'): ParentMessage =>\n declared.wrapper.make(\n Message.Announced({ message: text, politeness }),\n ) as unknown as ParentMessage\n\nexport const REGION_ATTRIBUTE = 'data-foldkit-plus-live'\n\n/**\n * The two live regions. Render once, anywhere in the page, and hide them\n * visually with a rule on `[data-foldkit-plus-live]`; they must stay in the\n * accessibility tree, so no `display: none`.\n */\nexport const view = <ParentMessage>(model: Model, h: HtmlBuilder<ParentMessage>): Html =>\n h.div(\n [h.Attribute(REGION_ATTRIBUTE, 'regions')],\n [\n h.div(\n [h.Attribute(REGION_ATTRIBUTE, 'polite'), h.AriaLive('polite'), h.AriaAtomic(true)],\n [model.polite],\n ),\n h.div(\n [h.Attribute(REGION_ATTRIBUTE, 'assertive'), h.AriaLive('assertive'), h.AriaAtomic(true)],\n [model.assertive],\n ),\n ],\n )\n","/**\n * Typeahead over a set of items: printable keys build a query that a timer\n * clears, and the query picks the item to move to. The query lives in the\n * Model so a replay sees the same picks; the timer runs on Effect's clock so\n * TestClock drives it. Which item matches is a pure function.\n */\nimport { Effect, Option, Schema } from 'effect'\nimport type { HtmlBuilder, KeyboardModifiers } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type * as Update from 'foldkit/update'\nimport { Bundle, type Declared } from 'foldkit-bundle'\nimport { Behavior, Behaviors, Capability } from 'foldkit-mixins'\nimport { idSelector } from './roving-tabindex.js'\n\nexport const Model = Schema.Struct({\n query: Schema.String,\n /** Bumped per keystroke, carried by the clear it schedules, so a superseded\n * timer's `Expired` changes nothing. */\n generation: Schema.Number,\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n /** A printable key was pressed with no ctrl, alt or meta. */\n Typed: { char: Schema.String },\n /** The clear timer fired for `generation`. */\n Expired: { generation: Schema.Number },\n /** The query was dropped on purpose (a blur, a selection). */\n Cleared: {},\n})\nexport type Message = typeof Message.Type\n\nexport const Args = Schema.Struct({\n /** How long the query survives after the last key. */\n timeoutMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n})\nexport type Args = typeof Args.Type\n\nexport const bundle = Bundle.make('Typeahead', {\n Model,\n Message,\n args: Args,\n init: () => ({ model: { query: '', generation: 0 } }),\n update: (model, message, args) =>\n Message.match<Update.Return<Model, Message>>(message, {\n Typed: ({ char }) => {\n const generation = model.generation + 1\n return {\n model: { query: model.query + char, generation },\n commands: [\n {\n name: 'Typeahead.expire',\n args: { generation },\n effect: Effect.as(Effect.sleep(args.timeoutMs), Message.Expired({ generation })),\n },\n ],\n }\n },\n Expired: ({ generation }) =>\n generation === model.generation ? { model: { ...model, query: '' } } : { model },\n Cleared: () => ({ model: { ...model, query: '' } }),\n }),\n})\n\nconst normalize = (text: string): string => text.trimStart().toLocaleLowerCase()\n\nconst isRepeated = (query: string): boolean =>\n query.length > 1 && [...query].every(char => char === query[0])\n\n/**\n * The enabled index whose text starts with `query`, searching forward from\n * `current` and wrapping, or `undefined`. A query of one character, or one\n * character repeated (`aaa`), starts *after* the current item so repeated\n * presses cycle through the items that begin with it; a longer query starts\n * *at* the current item, since the user is refining a match. Case and leading\n * whitespace are ignored.\n */\nexport const match = (\n texts: ReadonlyArray<string>,\n enabled: ReadonlyArray<number>,\n query: string,\n current: number,\n): number | undefined => {\n if (query === '' || enabled.length === 0) return undefined\n const needle = normalize(isRepeated(query) ? query[0]! : query)\n const position = enabled.indexOf(current)\n const startAfter = needle.length === 1 || isRepeated(query)\n const start = position === -1 ? 0 : startAfter ? position + 1 : position\n for (let offset = 0; offset < enabled.length; offset += 1) {\n const index = enabled[(start + offset) % enabled.length]!\n if (normalize(texts[index] ?? '').startsWith(needle)) return index\n }\n return undefined\n}\n\n/** A single character with no ctrl, alt or meta: a typeahead key. */\nexport const isPrintable = (key: string, modifiers: KeyboardModifiers): boolean =>\n key.length === 1 && !modifiers.ctrlKey && !modifiers.altKey && !modifiers.metaKey\n\nexport interface BehaviorOptions<Input, Slots> {\n /** The slot that receives the keys. */\n readonly host: keyof Slots & string\n readonly items: (input: Input) => Behaviors.Collection.Items<unknown>\n /** The text an item is matched by. */\n readonly text: (input: Input, index: number) => string\n /** The current item's id, so the search starts from it; a placed\n * `RovingTabindex` is the usual source. */\n readonly current: (input: Input) => string | null\n}\n\n/**\n * Wires a placed `Typeahead` to the host slot: a printable key extends the\n * query, focuses the matching item by its id (which then reports itself\n * current, through `RovingTabindex`'s `OnFocus`), and dispatches `Typed`.\n * With no match the key still extends the query, so the next one can match;\n * a space with an empty query is left to the host (it activates).\n */\nexport const behavior =\n <Field extends string>(declared: Declared<typeof bundle, Field>) =>\n <Slots>(slots: Slots) =>\n <Input extends { readonly [K in Field]: Model }, ParentMessage>(\n options: BehaviorOptions<Input, Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> => {\n const wrap = (char: string): ParentMessage =>\n declared.wrapper.make(Message.Typed({ char })) as unknown as ParentMessage\n return Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.host]: Behavior.slot({\n requires: { capability: Capability.Interactive },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) => {\n const items = options.items(input)\n const query = input[declared.field].query\n const currentId = options.current(input)\n const current = currentId === null ? -1 : items.indexOf(currentId)\n const texts = items.ids.map((_, index) => options.text(input, index))\n return [\n h.OnKeyDownFocus((key, modifiers) => {\n if (!isPrintable(key, modifiers)) return Option.none()\n if (key === ' ' && query === '') return Option.none()\n const found = match(texts, items.enabled, query + key, current)\n const target = found === undefined ? currentId : items.ids[found]\n return Option.some({\n // `:not(*)` matches nothing: no match and nothing current\n // moves no focus, but the key is still recorded.\n focusSelector:\n target === null || target === undefined ? ':not(*)' : idSelector(target),\n message: wrap(key),\n })\n }),\n ]\n },\n }),\n // Keyed by a value the caller chose; `forSlots` checks the key exists.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'Typeahead' },\n )\n }\n","/**\n * Keyboard navigation of a list in one placement: a roving tab stop, type to\n * find, and PageUp and PageDown. One Bundle holds the current item's id and\n * the typeahead query, so a printable key can move the pointer and extend the\n * query in one transition, which two placements cannot do under `virtual`.\n * The resolver allows one owner per event on a slot, so a host that needs\n * both arrows and typeahead takes this, not `RovingTabindex` plus `Typeahead`.\n */\nimport { Effect, Option, Schema } from 'effect'\nimport type { HtmlBuilder, KeyboardModifiers } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type * as Update from 'foldkit/update'\nimport { Bundle, type Declared } from 'foldkit-bundle'\nimport { Behavior, Behaviors, Capability, type SlotItem } from 'foldkit-mixins'\nimport * as RovingTabindex from './roving-tabindex.js'\nimport * as Typeahead from './typeahead.js'\n\nexport const Model = Schema.Struct({\n current: Schema.NullOr(Schema.String),\n query: Schema.String,\n generation: Schema.Number,\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n /** An item became current: focused, or reached by a key. */\n Focused: { id: Schema.String },\n /** A printable key extended the query; `match` is the item it now picks, if any. */\n Typed: { char: Schema.String, match: Schema.NullOr(Schema.String) },\n Expired: { generation: Schema.Number },\n Cleared: {},\n})\nexport type Message = typeof Message.Type\n\nexport const Args = Schema.Struct({\n orientation: RovingTabindex.Orientation,\n loop: Schema.Boolean,\n virtual: Schema.Boolean,\n /** How long the typeahead query survives after the last key. */\n timeoutMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n /** How many enabled items PageUp and PageDown move. */\n page: Schema.Number.pipe(Schema.check(Schema.isInt()), Schema.check(Schema.isGreaterThan(0))),\n})\nexport type Args = typeof Args.Type\n\nexport const bundle = Bundle.make('ListNavigation', {\n Model,\n Message,\n args: Args,\n init: () => ({ model: { current: null, query: '', generation: 0 } }),\n update: (model, message, args) =>\n Message.match<Update.Return<Model, Message>>(message, {\n Focused: ({ id }) => ({ model: { ...model, current: id } }),\n Typed: ({ char, match }) => {\n const generation = model.generation + 1\n return {\n model: { current: match ?? model.current, query: model.query + char, generation },\n commands: [\n {\n name: 'ListNavigation.expire',\n args: { generation },\n effect: Effect.as(Effect.sleep(args.timeoutMs), Message.Expired({ generation })),\n },\n ],\n }\n },\n Expired: ({ generation }) =>\n generation === model.generation ? { model: { ...model, query: '' } } : { model },\n Cleared: () => ({ model: { ...model, query: '' } }),\n }),\n})\n\nexport interface BehaviorOptions<Input, Slots> {\n readonly container: keyof Slots & string\n /** The slot rendered once per item; must carry the item's id (see `Behaviors.Collection`). */\n readonly item: keyof Slots & string\n readonly items: (input: Input) => Behaviors.Collection.Items<unknown>\n /** The text an item is matched by when typing. */\n readonly text: (input: Input, index: number) => string\n readonly direction?: (input: Input) => RovingTabindex.Direction\n}\n\n/**\n * The key's outcome, before it is turned into an attribute: navigation to an\n * id, a typed character with its match, or nothing.\n */\nexport type KeyOutcome =\n | { readonly _tag: 'Navigate'; readonly id: string }\n | { readonly _tag: 'Type'; readonly char: string; readonly match: string | null }\n\nexport const keyOutcome = (\n items: Behaviors.Collection.Items<unknown>,\n texts: ReadonlyArray<string>,\n state: { readonly current: string | null; readonly query: string },\n key: string,\n modifiers: KeyboardModifiers,\n options: RovingTabindex.MoveOptions,\n): KeyOutcome | undefined => {\n const from = state.current === null ? -1 : items.indexOf(state.current)\n const moved = RovingTabindex.move(items.enabled, from, key, modifiers, options)\n const movedId = moved === undefined ? undefined : items.ids[moved]\n if (movedId !== undefined) return { _tag: 'Navigate', id: movedId }\n if (!Typeahead.isPrintable(key, modifiers)) return undefined\n if (key === ' ' && state.query === '') return undefined\n const found = Typeahead.match(texts, items.enabled, state.query + key, from)\n return { _tag: 'Type', char: key, match: found === undefined ? null : (items.ids[found] ?? null) }\n}\n\n/**\n * Wires a placed `ListNavigation` to the slots. The container gets one key\n * handler: arrows, Home, End, PageUp and PageDown move by id; a printable key\n * extends the query and moves to its match. Each item gets `tabindex` and\n * `OnFocus` as under `RovingTabindex`. Under `virtual` nothing takes DOM focus\n * and the container carries `aria-activedescendant`.\n */\nexport const behavior =\n <Field extends string>(declared: Declared<typeof bundle, Field>, args: Args) =>\n <Slots>(slots: Slots) =>\n <Input extends { readonly [K in Field]: Model }, ParentMessage>(\n options: BehaviorOptions<Input, Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> => {\n const wrap = (message: Message): ParentMessage =>\n declared.wrapper.make(message) as unknown as ParentMessage\n const focused = (id: string): ParentMessage => wrap(Message.Focused({ id }))\n const slice = (input: Input): Model => input[declared.field]\n const outcomeMessage = (outcome: KeyOutcome): ParentMessage =>\n outcome._tag === 'Navigate'\n ? focused(outcome.id)\n : wrap(Message.Typed({ char: outcome.char, match: outcome.match }))\n return Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.container]: Behavior.slot({\n requires: { capability: Capability.Interactive },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) => {\n const items = options.items(input)\n const state = slice(input)\n const texts = items.ids.map((_, index) => options.text(input, index))\n const moveOptions: RovingTabindex.MoveOptions = {\n orientation: args.orientation,\n loop: args.loop,\n direction: options.direction?.(input) ?? 'ltr',\n page: args.page,\n }\n const outcome = (key: string, modifiers: KeyboardModifiers) =>\n Option.fromNullishOr(keyOutcome(items, texts, state, key, modifiers, moveOptions))\n const stop = RovingTabindex.tabStop(items, state.current)\n const stopId = stop === -1 ? undefined : items.ids[stop]\n if (args.virtual) {\n return [\n h.OnKeyDownPreventDefault((key, modifiers) =>\n Option.map(outcome(key, modifiers), outcomeMessage),\n ),\n ...(stopId === undefined ? [] : [h.AriaActiveDescendant(stopId)]),\n ]\n }\n return [\n h.OnKeyDownFocus((key, modifiers) =>\n Option.map(outcome(key, modifiers), result => {\n const target =\n result._tag === 'Navigate' ? result.id : (result.match ?? state.current)\n return {\n // `:not(*)` matches nothing: a typed key with no match and\n // nothing current moves no focus but is still recorded.\n focusSelector: target === null ? ':not(*)' : RovingTabindex.idSelector(target),\n message: outcomeMessage(result),\n }\n }),\n ),\n ]\n },\n }),\n [options.item]: Behavior.slot({\n requires: { capability: Capability.Focusable },\n attributes: ({\n input,\n h,\n item,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n readonly item?: SlotItem\n }) =>\n item === undefined\n ? []\n : RovingTabindex.itemAttributes(\n h,\n options.items(input),\n slice(input).current,\n item,\n args.virtual,\n focused,\n ),\n }),\n // Keyed by values the caller chose; `forSlots` checks both keys exist.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'ListNavigation' },\n )\n }\n","/**\n * Press: pointer and keyboard activation of one element as one fact. A Mount\n * reports what the element saw (pointer down, up, cancel, key down, up,\n * click) with the detail Foldkit's declarative attributes do not carry; the\n * Bundle's `update` decides what counts as a press; the ghost click that\n * follows a touch is suppressed by a timed Command on Effect's clock, never\n * by a clock read in a handler. Activation is an OutMessage, `Pressed`, so\n * the placement must say what a press does.\n */\nimport { Effect, Queue, Schema, Stream } from 'effect'\nimport type { HtmlBuilder } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Mount from 'foldkit/mount'\nimport type * as Update from 'foldkit/update'\nimport { Bundle, type Declared } from 'foldkit-bundle'\nimport { Behavior, Capability } from 'foldkit-mixins'\n\nexport const PointerType = Schema.Literals(['mouse', 'touch', 'pen', 'keyboard', 'virtual'])\nexport type PointerType = typeof PointerType.Type\n\nexport const Model = Schema.Struct({\n /** Down and not yet released or cancelled. For styling. */\n pressed: Schema.Boolean,\n /** The pointer that pressed, so another pointer's up cannot release it. */\n pointerId: Schema.NullOr(Schema.Number),\n /** Enter or Space is down. */\n key: Schema.NullOr(Schema.String),\n /** A click arriving before this generation's window ends is the ghost of a pointer press. */\n suppressing: Schema.NullOr(Schema.Number),\n generation: Schema.Number,\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n PointerDown: { pointerId: Schema.Number, button: Schema.Number, pointerType: Schema.String },\n PointerUp: { pointerId: Schema.Number, pointerType: Schema.String, shiftKey: Schema.Boolean },\n PointerCancelled: { pointerId: Schema.Number },\n KeyDown: { key: Schema.String, repeat: Schema.Boolean },\n KeyUp: { key: Schema.String, shiftKey: Schema.Boolean },\n /** `detail` is 0 for a click no pointer made: keyboard on a native control, or assistive technology. */\n Clicked: { detail: Schema.Number, shiftKey: Schema.Boolean },\n Unsuppressed: { generation: Schema.Number },\n})\nexport type Message = typeof Message.Type\n\nexport const Pressed = Schema.TaggedStruct('Pressed', {\n pointerType: PointerType,\n /** Shift was held, for a range selection. */\n shiftKey: Schema.Boolean,\n})\nexport type Pressed = typeof Pressed.Type\n\nexport const Args = Schema.Struct({\n /** How long after a pointer press a `click` is taken for its ghost. */\n clickSuppressionMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThanOrEqualTo(0)),\n Schema.check(Schema.isFinite()),\n ),\n})\nexport type Args = typeof Args.Type\n\nconst idle = (model: Model): Model => ({ ...model, pressed: false, pointerId: null, key: null })\n\nconst isActivationKey = (key: string): boolean => key === 'Enter' || key === ' '\n\nconst asPointerType = (raw: string): PointerType =>\n raw === 'touch' || raw === 'pen' ? raw : 'mouse'\n\ntype Return = Update.ReturnWithOutMessage<Model, Message, Pressed>\n\nexport const bundle = Bundle.make('Press', {\n Model,\n Message,\n args: Args,\n init: () => ({\n model: { pressed: false, pointerId: null, key: null, suppressing: null, generation: 0 },\n }),\n update: (model, message, args): Return =>\n Message.match<Return>(message, {\n PointerDown: ({ pointerId, button }) =>\n button !== 0 || model.pointerId !== null\n ? { model }\n : { model: { ...model, pressed: true, pointerId } },\n PointerUp: ({ pointerId, pointerType, shiftKey }) => {\n if (model.pointerId !== pointerId) return { model }\n const generation = model.generation + 1\n return {\n model: { ...idle(model), suppressing: generation, generation },\n commands: [\n {\n name: 'Press.unsuppress',\n args: { generation },\n effect: Effect.as(\n Effect.sleep(args.clickSuppressionMs),\n Message.Unsuppressed({ generation }),\n ),\n },\n ],\n outMessage: Pressed.make({ pointerType: asPointerType(pointerType), shiftKey }),\n }\n },\n PointerCancelled: ({ pointerId }) =>\n model.pointerId === pointerId ? { model: idle(model) } : { model },\n KeyDown: ({ key, repeat }) =>\n !isActivationKey(key) || repeat || model.key !== null || model.pointerId !== null\n ? { model }\n : { model: { ...model, pressed: true, key } },\n KeyUp: ({ key, shiftKey }) =>\n model.key === key\n ? { model: idle(model), outMessage: Pressed.make({ pointerType: 'keyboard', shiftKey }) }\n : { model },\n Clicked: ({ detail, shiftKey }) =>\n detail === 0\n ? { model, outMessage: Pressed.make({ pointerType: 'virtual', shiftKey }) }\n : model.suppressing !== null\n ? { model }\n : { model, outMessage: Pressed.make({ pointerType: 'mouse', shiftKey }) },\n Unsuppressed: ({ generation }) =>\n model.suppressing === generation ? { model: { ...model, suppressing: null } } : { model },\n }),\n})\n\ntype PointerLike = Event & {\n readonly pointerId?: number\n readonly button?: number\n readonly pointerType?: string\n readonly shiftKey?: boolean\n}\n\n/** What the element reports; `Unsuppressed` comes from the timer, not the DOM. */\nexport type Fact = Exclude<Message, { readonly _tag: 'Unsuppressed' }>\n\n/**\n * The element's press-relevant events as facts. Enter and Space key downs\n * are default-prevented so a native control does not also click, and Space\n * does not scroll; the Bundle owns activation. An element marked\n * `aria-disabled=\"true\"` (which the Behavior writes) reports nothing, read\n * at event time so a toggle needs no remount. Nothing else is decided here.\n */\nexport const events = Mount.defineStream('PressEvents', {\n messages: [\n Message.PointerDown,\n Message.PointerUp,\n Message.PointerCancelled,\n Message.KeyDown,\n Message.KeyUp,\n Message.Clicked,\n ],\n execute: ({ element }) =>\n Stream.callback<Fact>(queue =>\n Effect.acquireRelease(\n Effect.sync(() => {\n const offer = (message: Fact) => {\n if (element.getAttribute('aria-disabled') === 'true') return\n Queue.offerUnsafe(queue, message)\n }\n const listeners: ReadonlyArray<readonly [string, (event: Event) => void]> = [\n [\n 'pointerdown',\n event => {\n const pointer = event as PointerLike\n offer(\n Message.PointerDown({\n pointerId: pointer.pointerId ?? 0,\n button: pointer.button ?? 0,\n pointerType: pointer.pointerType ?? 'mouse',\n }),\n )\n },\n ],\n [\n 'pointerup',\n event => {\n const pointer = event as PointerLike\n offer(\n Message.PointerUp({\n pointerId: pointer.pointerId ?? 0,\n pointerType: pointer.pointerType ?? 'mouse',\n shiftKey: pointer.shiftKey ?? false,\n }),\n )\n },\n ],\n [\n 'pointerleave',\n event =>\n offer(\n Message.PointerCancelled({ pointerId: (event as PointerLike).pointerId ?? 0 }),\n ),\n ],\n [\n 'pointercancel',\n event =>\n offer(\n Message.PointerCancelled({ pointerId: (event as PointerLike).pointerId ?? 0 }),\n ),\n ],\n [\n 'keydown',\n event => {\n const key = event as KeyboardEvent\n if (isActivationKey(key.key)) key.preventDefault()\n offer(Message.KeyDown({ key: key.key, repeat: key.repeat }))\n },\n ],\n [\n 'keyup',\n event => {\n const key = event as KeyboardEvent\n offer(Message.KeyUp({ key: key.key, shiftKey: key.shiftKey }))\n },\n ],\n [\n 'click',\n event => {\n const click = event as MouseEvent\n offer(\n Message.Clicked({ detail: click.detail ?? 0, shiftKey: click.shiftKey ?? false }),\n )\n },\n ],\n ]\n for (const [type, listener] of listeners) element.addEventListener(type, listener)\n return listeners\n }),\n listeners =>\n Effect.sync(() => {\n for (const [type, listener] of listeners) element.removeEventListener(type, listener)\n }),\n ),\n ),\n})\n\nexport interface BehaviorOptions<Input, Slots> {\n readonly target: keyof Slots & string\n /** Marks the target `aria-disabled`, which also silences its events. */\n readonly disabled?: (input: Input) => boolean\n}\n\n/**\n * Attaches the events Mount to the target slot, mapped into the placement's\n * Messages, and writes `data-pressed` while down for styling. The element\n * decides nothing; a click, a touch, Enter, Space, or an assistive\n * technology's click all reach `update` and come out as one `Pressed`.\n */\nexport const behavior =\n <Field extends string>(declared: Declared<typeof bundle, Field>) =>\n <Slots>(slots: Slots) =>\n <Input extends { readonly [K in Field]: Model }, ParentMessage>(\n options: BehaviorOptions<Input, Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.target]: Behavior.slot({\n requires: { capability: Capability.Interactive },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) => [\n ...(input[declared.field].pressed ? [h.DataAttribute('pressed', 'true')] : []),\n ...(options.disabled?.(input) === true ? [h.AriaDisabled(true)] : []),\n ],\n mount: () =>\n Mount.mapMessage(\n events(),\n (message): ParentMessage =>\n declared.wrapper.make(message) as unknown as ParentMessage,\n ),\n }),\n // Keyed by a value the caller chose; `forSlots` checks the key exists.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'Press' },\n )\n","/**\n * LongPress: holding an element down for `thresholdMs` is one fact. It reads\n * the same facts `Press.events` reports; the threshold is a Command on\n * Effect's clock carrying a generation, so a release before it fires makes\n * its `Elapsed` a no-op. `LongPressed` is an OutMessage the placement must\n * handle. A short press is `Press`'s business, not this Bundle's.\n */\nimport { Effect, Schema } from 'effect'\nimport type { HtmlBuilder } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport * as Mount from 'foldkit/mount'\nimport type * as Update from 'foldkit/update'\nimport { Bundle, type Declared } from 'foldkit-bundle'\nimport { Behavior, Capability } from 'foldkit-mixins'\nimport * as Press from './press.js'\n\nexport const Model = Schema.Struct({\n /** Down, and the threshold has not passed. */\n holding: Schema.Boolean,\n pointerId: Schema.NullOr(Schema.Number),\n key: Schema.NullOr(Schema.String),\n generation: Schema.Number,\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n PointerDown: { pointerId: Schema.Number, button: Schema.Number, pointerType: Schema.String },\n PointerUp: { pointerId: Schema.Number, pointerType: Schema.String, shiftKey: Schema.Boolean },\n PointerCancelled: { pointerId: Schema.Number },\n KeyDown: { key: Schema.String, repeat: Schema.Boolean },\n KeyUp: { key: Schema.String, shiftKey: Schema.Boolean },\n Clicked: { detail: Schema.Number, shiftKey: Schema.Boolean },\n Elapsed: { generation: Schema.Number, pointerType: Press.PointerType },\n})\nexport type Message = typeof Message.Type\n\nexport const LongPressed = Schema.TaggedStruct('LongPressed', { pointerType: Press.PointerType })\nexport type LongPressed = typeof LongPressed.Type\n\nexport const Args = Schema.Struct({\n thresholdMs: Schema.Number.pipe(\n Schema.check(Schema.isGreaterThan(0)),\n Schema.check(Schema.isFinite()),\n ),\n})\nexport type Args = typeof Args.Type\n\nconst isActivationKey = (key: string): boolean => key === 'Enter' || key === ' '\n\nconst released = (model: Model): Model => ({ ...model, holding: false, pointerId: null, key: null })\n\ntype Return = Update.ReturnWithOutMessage<Model, Message, LongPressed>\n\nconst start = (\n model: Model,\n args: Args,\n by: { readonly pointerId: number } | { readonly key: string },\n pointerType: Press.PointerType,\n): Return => {\n const generation = model.generation + 1\n return {\n model: {\n holding: true,\n pointerId: 'pointerId' in by ? by.pointerId : null,\n key: 'key' in by ? by.key : null,\n generation,\n },\n commands: [\n {\n name: 'LongPress.elapse',\n args: { generation },\n effect: Effect.as(\n Effect.sleep(args.thresholdMs),\n Message.Elapsed({ generation, pointerType }),\n ),\n },\n ],\n }\n}\n\nexport const bundle = Bundle.make('LongPress', {\n Model,\n Message,\n args: Args,\n init: () => ({ model: { holding: false, pointerId: null, key: null, generation: 0 } }),\n update: (model, message, args): Return =>\n Message.match<Return>(message, {\n PointerDown: ({ pointerId, button, pointerType }) =>\n button !== 0 || model.holding\n ? { model }\n : start(\n model,\n args,\n { pointerId },\n pointerType === 'touch' || pointerType === 'pen' ? pointerType : 'mouse',\n ),\n PointerUp: ({ pointerId }) =>\n model.pointerId === pointerId ? { model: released(model) } : { model },\n PointerCancelled: ({ pointerId }) =>\n model.pointerId === pointerId ? { model: released(model) } : { model },\n KeyDown: ({ key, repeat }) =>\n !isActivationKey(key) || repeat || model.holding\n ? { model }\n : start(model, args, { key }, 'keyboard'),\n KeyUp: ({ key }) => (model.key === key ? { model: released(model) } : { model }),\n Clicked: () => ({ model }),\n Elapsed: ({ generation, pointerType }) =>\n model.holding && model.generation === generation\n ? { model: released(model), outMessage: LongPressed.make({ pointerType }) }\n : { model },\n }),\n})\n\nexport interface BehaviorOptions<Slots> {\n readonly target: keyof Slots & string\n}\n\n/**\n * Attaches `Press.events` to the target, mapped into this placement's\n * Messages, and writes `data-holding` while the element is down. `Press` and\n * `LongPress` on one slot are refused by the resolver (two Mounts of one\n * name), so today a slot takes one of them.\n */\nexport const behavior =\n <Field extends string>(declared: Declared<typeof bundle, Field>) =>\n <Slots>(slots: Slots) =>\n <Input extends { readonly [K in Field]: Model }, ParentMessage>(\n options: BehaviorOptions<Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.target]: Behavior.slot({\n requires: { capability: Capability.Interactive },\n attributes: ({\n input,\n h,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n }) => (input[declared.field].holding ? [h.DataAttribute('holding', 'true')] : []),\n mount: () =>\n Mount.mapMessage(\n Press.events(),\n // The facts are the same tagged structs; only the union differs.\n (fact): ParentMessage =>\n declared.wrapper.make(fact as unknown as Message) as unknown as ParentMessage,\n ),\n }),\n // Keyed by a value the caller chose; `forSlots` checks the key exists.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'LongPress' },\n )\n","/**\n * `Move` as a Behavior: attaches the `foldkit-primitives/dom` Mount to a\n * draggable slot and maps its facts into the view's Messages. No Bundle: a\n * drag's meaning (a threshold, a snap, a reorder) is the parent's `update`,\n * and the deltas are already facts.\n */\nimport * as Mount from 'foldkit/mount'\nimport { Behavior, Capability } from 'foldkit-mixins'\nimport { Move as MoveMount, type Fact } from '../dom/move.js'\n\nexport { Move as mount, MoveEnded, MoveStarted, Moved, type Fact } from '../dom/move.js'\n\nexport interface BehaviorOptions<Slots, ParentMessage> {\n readonly handle: keyof Slots & string\n /** Turns each fact into the view's Message. */\n readonly toMessage: (fact: Fact) => ParentMessage\n}\n\nexport const behavior =\n <Slots>(slots: Slots) =>\n <Input, ParentMessage>(\n options: BehaviorOptions<Slots, ParentMessage>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> =>\n Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n [options.handle]: Behavior.slot({\n requires: { capability: Capability.Draggable },\n mount: () => Mount.mapMessage(MoveMount(), options.toMessage),\n }),\n // Keyed by a value the caller chose; `forSlots` checks the key exists.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'Move' },\n )\n","/**\n * Selection over a set of items: which ids are selected, and the anchor a\n * range extends from. `mode` is `'single'` (a click replaces), `'multiple'`\n * (a click toggles), or `'none'`. Single mode keeps one item selected unless\n * `allowEmpty`. A range needs the items' order, which the view knows and the\n * Bundle does not, so `Ranged` carries it. The Behavior says the state in\n * ARIA and wires a plain click; a Shift range comes from `Press`, whose\n * `Pressed` carries `shiftKey`.\n */\nimport { Schema } from 'effect'\nimport type { HtmlBuilder } from 'foldkit/html'\nimport { defineMessageUnion } from 'foldkit/message'\nimport { Bundle, type Declared } from 'foldkit-bundle'\nimport { Behavior, Behaviors, Capability, type SlotItem } from 'foldkit-mixins'\n\nexport const Mode = Schema.Literals(['single', 'multiple', 'none'])\nexport type Mode = typeof Mode.Type\n\nexport const Model = Schema.Struct({\n /** In selection order. */\n selected: Schema.Array(Schema.String),\n /** Where the last plain selection happened; a range extends from it. */\n anchor: Schema.NullOr(Schema.String),\n})\nexport type Model = typeof Model.Type\n\nexport const Message = defineMessageUnion({\n /** A plain activation: replaces in single mode, toggles in multiple mode. */\n Activated: { id: Schema.String },\n /** A Shift activation: selects everything between the anchor and `id` in `order`. */\n Ranged: { id: Schema.String, order: Schema.Array(Schema.String) },\n Replaced: { ids: Schema.Array(Schema.String) },\n Cleared: {},\n})\nexport type Message = typeof Message.Type\n\nexport const Args = Schema.Struct({\n mode: Mode,\n /** In single mode, whether activating the selected item deselects it. */\n allowEmpty: Schema.Boolean,\n})\nexport type Args = typeof Args.Type\n\nconst without = (ids: ReadonlyArray<string>, id: string) => ids.filter(other => other !== id)\n\n/** The ids between `from` and `to` in `order`, inclusive, in `order`'s order. */\nexport const between = (\n order: ReadonlyArray<string>,\n from: string | null,\n to: string,\n): ReadonlyArray<string> => {\n const end = order.indexOf(to)\n if (end === -1) return []\n const start = from === null ? -1 : order.indexOf(from)\n if (start === -1) return [to]\n return order.slice(Math.min(start, end), Math.max(start, end) + 1)\n}\n\nexport const bundle = Bundle.make('Selection', {\n Model,\n Message,\n args: Args,\n init: () => ({ model: { selected: [], anchor: null } }),\n update: (model, message, args) =>\n Message.match(message, {\n Activated: ({ id }) => {\n if (args.mode === 'none') return { model }\n const isSelected = model.selected.includes(id)\n if (args.mode === 'single') {\n return isSelected\n ? args.allowEmpty\n ? { model: { selected: [], anchor: id } }\n : { model }\n : { model: { selected: [id], anchor: id } }\n }\n return {\n model: {\n selected: isSelected ? without(model.selected, id) : [...model.selected, id],\n anchor: id,\n },\n }\n },\n Ranged: ({ id, order }) => {\n if (args.mode !== 'multiple') return { model }\n const range = between(order, model.anchor, id)\n const kept = model.selected.filter(other => !range.includes(other))\n return { model: { selected: [...kept, ...range], anchor: model.anchor ?? id } }\n },\n Replaced: ({ ids }) => ({\n model: { selected: args.mode === 'none' ? [] : [...new Set(ids)], anchor: model.anchor },\n }),\n Cleared: () => ({ model: { selected: [], anchor: model.anchor } }),\n }),\n})\n\nexport interface BehaviorOptions<Input, Slots> {\n /** The slot holding the items, for `aria-multiselectable`. */\n readonly container?: keyof Slots & string\n /** The slot rendered once per item; must carry the item's id. */\n readonly item: keyof Slots & string\n readonly items: (input: Input) => Behaviors.Collection.Items<unknown>\n /** Wire a plain click on each item to `Activated`. Default `true`; turn off\n * when `Press` or the view owns the click. */\n readonly click?: boolean\n}\n\n/**\n * Writes `aria-selected` on each item from the placed slice, and\n * `aria-multiselectable` on the container in multiple mode; wires `OnClick`\n * to `Activated` unless told not to. A disabled item gets no click.\n */\nexport const behavior =\n <Field extends string>(declared: Declared<typeof bundle, Field>, args: Args) =>\n <Slots>(slots: Slots) =>\n <Input extends { readonly [K in Field]: Model }, ParentMessage>(\n options: BehaviorOptions<Input, Slots>,\n ): Behavior.NamedBehavior<Slots, Input, ParentMessage> => {\n const wrap = (message: Message): ParentMessage =>\n declared.wrapper.make(message) as unknown as ParentMessage\n return Behavior.forSlots(slots)<Input, ParentMessage>(\n {\n ...(options.container === undefined\n ? {}\n : {\n [options.container]: Behavior.slot({\n requires: { capability: Capability.Container },\n attributes: ({ h }: { readonly h: HtmlBuilder<ParentMessage> }) => [\n h.AriaMultiSelectable(args.mode === 'multiple'),\n ],\n }),\n }),\n [options.item]: Behavior.slot({\n requires: { capability: Capability.Interactive },\n attributes: ({\n input,\n h,\n item,\n }: {\n readonly input: Input\n readonly h: HtmlBuilder<ParentMessage>\n readonly item?: SlotItem\n }) => {\n if (item === undefined) return []\n const items = options.items(input)\n const id = item.id ?? items.ids[item.index]\n if (id === undefined) return []\n const clickable =\n options.click !== false && args.mode !== 'none' && !items.isDisabled(item.index)\n return [\n h.AriaSelected(input[declared.field].selected.includes(id)),\n ...(clickable ? [h.OnClick(wrap(Message.Activated({ id })))] : []),\n ]\n },\n }),\n // Keyed by values the caller chose; `forSlots` checks the keys exist.\n } as unknown as Behavior.BehaviorSpec<Slots, Input, ParentMessage>,\n { name: 'Selection' },\n )\n }\n"],"mappings":"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"}
|
package/dist/motion/index.d.mts
CHANGED
|
@@ -1,112 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
//#region src/motion/presence.d.ts
|
|
4
|
-
export declare const PresenceModel: Schema.Struct<{
|
|
5
|
-
readonly phase: Schema.Literals<readonly ["shown", "hiding", "hidden"]>;
|
|
6
|
-
readonly generation: Schema.Number;
|
|
7
|
-
}>;
|
|
8
|
-
export type PresenceModel = typeof PresenceModel.Type;
|
|
9
|
-
export declare const PresenceMessage: import("foldkit/message").MessageUnion<{
|
|
10
|
-
readonly Show: {};
|
|
11
|
-
readonly Hide: {};
|
|
12
|
-
readonly Hidden: {
|
|
13
|
-
readonly generation: Schema.Number;
|
|
14
|
-
};
|
|
15
|
-
}>;
|
|
16
|
-
export type PresenceMessage = typeof PresenceMessage.Type;
|
|
17
|
-
/** Whether the content renders: shown or mid-exit. */
|
|
18
|
-
export declare const isVisible: (model: PresenceModel) => boolean;
|
|
19
|
-
export declare const Presence: Bundle.Bundle<"Presence", {
|
|
20
|
-
readonly durationMs: number;
|
|
21
|
-
}, {
|
|
22
|
-
readonly phase: "shown" | "hiding" | "hidden";
|
|
23
|
-
readonly generation: number;
|
|
24
|
-
}, {
|
|
25
|
-
readonly _tag: "Show";
|
|
26
|
-
} | {
|
|
27
|
-
readonly _tag: "Hide";
|
|
28
|
-
} | {
|
|
29
|
-
readonly _tag: "Hidden";
|
|
30
|
-
readonly generation: number;
|
|
31
|
-
}, never, never, never, void, {}, {}>;
|
|
32
|
-
//#endregion
|
|
33
|
-
//#region src/motion/spring.d.ts
|
|
34
|
-
export declare const SpringModel: Schema.Struct<{
|
|
35
|
-
readonly value: Schema.Number;
|
|
36
|
-
readonly velocity: Schema.Number;
|
|
37
|
-
readonly running: Schema.Boolean;
|
|
38
|
-
}>;
|
|
39
|
-
export type SpringModel = typeof SpringModel.Type;
|
|
40
|
-
export declare const SpringMessage: import("foldkit/message").MessageUnion<{
|
|
41
|
-
readonly Started: {};
|
|
42
|
-
readonly Stopped: {};
|
|
43
|
-
readonly Ticked: {
|
|
44
|
-
readonly value: Schema.Number;
|
|
45
|
-
readonly velocity: Schema.Number;
|
|
46
|
-
};
|
|
47
|
-
/** Carries the exact end value: the integrator stops near it, not on it. */
|
|
48
|
-
readonly Finished: {
|
|
49
|
-
readonly value: Schema.Number;
|
|
50
|
-
};
|
|
51
|
-
}>;
|
|
52
|
-
export type SpringMessage = typeof SpringMessage.Type;
|
|
53
|
-
export declare const Spring: Bundle.Bundle<"Spring", {
|
|
54
|
-
readonly from: number;
|
|
55
|
-
readonly to: number;
|
|
56
|
-
readonly stiffness: number;
|
|
57
|
-
readonly damping: number;
|
|
58
|
-
}, {
|
|
59
|
-
readonly value: number;
|
|
60
|
-
readonly velocity: number;
|
|
61
|
-
readonly running: boolean;
|
|
62
|
-
}, {
|
|
63
|
-
readonly _tag: "Started";
|
|
64
|
-
} | {
|
|
65
|
-
readonly _tag: "Stopped";
|
|
66
|
-
} | {
|
|
67
|
-
readonly _tag: "Ticked";
|
|
68
|
-
readonly value: number;
|
|
69
|
-
readonly velocity: number;
|
|
70
|
-
} | {
|
|
71
|
-
readonly _tag: "Finished";
|
|
72
|
-
readonly value: number;
|
|
73
|
-
}, never, never, never, void, {}, {}>;
|
|
74
|
-
//#endregion
|
|
75
|
-
//#region src/motion/tween.d.ts
|
|
76
|
-
export declare const TweenModel: Schema.Struct<{
|
|
77
|
-
readonly value: Schema.Number;
|
|
78
|
-
readonly running: Schema.Boolean;
|
|
79
|
-
}>;
|
|
80
|
-
export type TweenModel = typeof TweenModel.Type;
|
|
81
|
-
export declare const TweenMessage: import("foldkit/message").MessageUnion<{
|
|
82
|
-
readonly Started: {};
|
|
83
|
-
readonly Stopped: {};
|
|
84
|
-
readonly Ticked: {
|
|
85
|
-
readonly value: Schema.Number;
|
|
86
|
-
};
|
|
87
|
-
/** Carries the exact end value: the stream may stop on either side of it. */
|
|
88
|
-
readonly Finished: {
|
|
89
|
-
readonly value: Schema.Number;
|
|
90
|
-
};
|
|
91
|
-
}>;
|
|
92
|
-
export type TweenMessage = typeof TweenMessage.Type;
|
|
93
|
-
export declare const Tween: Bundle.Bundle<"Tween", {
|
|
94
|
-
readonly from: number;
|
|
95
|
-
readonly to: number;
|
|
96
|
-
readonly ms: number;
|
|
97
|
-
}, {
|
|
98
|
-
readonly value: number;
|
|
99
|
-
readonly running: boolean;
|
|
100
|
-
}, {
|
|
101
|
-
readonly _tag: "Started";
|
|
102
|
-
} | {
|
|
103
|
-
readonly _tag: "Stopped";
|
|
104
|
-
} | {
|
|
105
|
-
readonly _tag: "Ticked";
|
|
106
|
-
readonly value: number;
|
|
107
|
-
} | {
|
|
108
|
-
readonly _tag: "Finished";
|
|
109
|
-
readonly value: number;
|
|
110
|
-
}, never, never, never, void, {}, {}>;
|
|
111
|
-
//#endregion
|
|
112
|
-
//# sourceMappingURL=index.d.mts.map
|
|
1
|
+
import { a as SpringMessage, c as PresenceMessage, d as motion_d_exports, i as Spring, l as PresenceModel, n as TweenMessage, o as SpringModel, r as TweenModel, s as Presence, t as Tween, u as isVisible } from "../index-Cppf-lBx.mjs";
|
|
2
|
+
export { motion_d_exports as Motion, Presence, PresenceMessage, PresenceModel, Spring, SpringMessage, SpringModel, Tween, TweenMessage, TweenModel, isVisible };
|
package/dist/motion/index.mjs
CHANGED
|
@@ -1,208 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import * as Subscription from "foldkit/subscription";
|
|
4
|
-
import { Bundle } from "foldkit-bundle";
|
|
5
|
-
//#region src/motion/presence.ts
|
|
6
|
-
/**
|
|
7
|
-
* Presence as a bundle: mount-transition state for exit animations. `Hide`
|
|
8
|
-
* starts a timed `hiding` phase; the `Hidden` fact it yields carries the
|
|
9
|
-
* generation it was scheduled under, so a `Show` in between wins and the
|
|
10
|
-
* late fact is ignored. The clock is Effect's, so tests drive it.
|
|
11
|
-
*/
|
|
12
|
-
const PresenceModel = Schema.Struct({
|
|
13
|
-
phase: Schema.Literals([
|
|
14
|
-
"shown",
|
|
15
|
-
"hiding",
|
|
16
|
-
"hidden"
|
|
17
|
-
]),
|
|
18
|
-
generation: Schema.Number
|
|
19
|
-
});
|
|
20
|
-
const PresenceMessage = defineMessageUnion({
|
|
21
|
-
Show: {},
|
|
22
|
-
Hide: {},
|
|
23
|
-
Hidden: { generation: Schema.Number }
|
|
24
|
-
});
|
|
25
|
-
/** Whether the content renders: shown or mid-exit. */
|
|
26
|
-
const isVisible = (model) => model.phase !== "hidden";
|
|
27
|
-
const Presence = Bundle.make("Presence", {
|
|
28
|
-
Model: PresenceModel,
|
|
29
|
-
Message: PresenceMessage,
|
|
30
|
-
args: Schema.Struct({ durationMs: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())) }),
|
|
31
|
-
init: () => ({ model: {
|
|
32
|
-
phase: "hidden",
|
|
33
|
-
generation: 0
|
|
34
|
-
} }),
|
|
35
|
-
update: (model, message, args) => PresenceMessage.match(message, {
|
|
36
|
-
Show: () => ({ model: {
|
|
37
|
-
...model,
|
|
38
|
-
phase: "shown",
|
|
39
|
-
generation: model.generation + 1
|
|
40
|
-
} }),
|
|
41
|
-
Hide: () => {
|
|
42
|
-
const generation = model.generation + 1;
|
|
43
|
-
return {
|
|
44
|
-
model: {
|
|
45
|
-
...model,
|
|
46
|
-
phase: "hiding",
|
|
47
|
-
generation
|
|
48
|
-
},
|
|
49
|
-
commands: [{
|
|
50
|
-
name: "Presence.hide",
|
|
51
|
-
args: { generation },
|
|
52
|
-
effect: Effect.as(Effect.sleep(args.durationMs), PresenceMessage.Hidden({ generation }))
|
|
53
|
-
}]
|
|
54
|
-
};
|
|
55
|
-
},
|
|
56
|
-
Hidden: ({ generation }) => generation === model.generation ? { model: {
|
|
57
|
-
...model,
|
|
58
|
-
phase: "hidden"
|
|
59
|
-
} } : { model }
|
|
60
|
-
})
|
|
61
|
-
});
|
|
62
|
-
//#endregion
|
|
63
|
-
//#region src/motion/spring.ts
|
|
64
|
-
/**
|
|
65
|
-
* A spring as a bundle: a number pulled toward a target with stiffness and
|
|
66
|
-
* damping, in the Model. Semi-implicit Euler at a fixed 16ms step, so the
|
|
67
|
-
* trajectory is deterministic under TestClock; the stream ends with
|
|
68
|
-
* Finished, and the value rests exactly at `to`. Underdamped springs
|
|
69
|
-
* overshoot — that is the point — but the rest value is still exact.
|
|
70
|
-
*/
|
|
71
|
-
const SpringModel = Schema.Struct({
|
|
72
|
-
value: Schema.Number,
|
|
73
|
-
velocity: Schema.Number,
|
|
74
|
-
running: Schema.Boolean
|
|
75
|
-
});
|
|
76
|
-
const SpringMessage = defineMessageUnion({
|
|
77
|
-
Started: {},
|
|
78
|
-
Stopped: {},
|
|
79
|
-
Ticked: {
|
|
80
|
-
value: Schema.Number,
|
|
81
|
-
velocity: Schema.Number
|
|
82
|
-
},
|
|
83
|
-
/** Carries the exact end value: the integrator stops near it, not on it. */
|
|
84
|
-
Finished: { value: Schema.Number }
|
|
85
|
-
});
|
|
86
|
-
const step = 16;
|
|
87
|
-
const epsilon = .001;
|
|
88
|
-
const settled = (point, to) => Math.abs(to - point.x) < epsilon && Math.abs(point.v) < epsilon;
|
|
89
|
-
const Spring = Bundle.make("Spring", {
|
|
90
|
-
Model: SpringModel,
|
|
91
|
-
Message: SpringMessage,
|
|
92
|
-
args: Schema.Struct({
|
|
93
|
-
from: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
94
|
-
to: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
95
|
-
stiffness: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite())),
|
|
96
|
-
damping: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite()))
|
|
97
|
-
}),
|
|
98
|
-
init: (args) => ({ model: {
|
|
99
|
-
value: args.from,
|
|
100
|
-
velocity: 0,
|
|
101
|
-
running: false
|
|
102
|
-
} }),
|
|
103
|
-
update: (model, message) => SpringMessage.match(message, {
|
|
104
|
-
Started: () => ({ model: {
|
|
105
|
-
...model,
|
|
106
|
-
running: true
|
|
107
|
-
} }),
|
|
108
|
-
Stopped: () => ({ model: {
|
|
109
|
-
...model,
|
|
110
|
-
running: false
|
|
111
|
-
} }),
|
|
112
|
-
Ticked: ({ value, velocity }) => ({ model: {
|
|
113
|
-
...model,
|
|
114
|
-
value,
|
|
115
|
-
velocity
|
|
116
|
-
} }),
|
|
117
|
-
Finished: ({ value }) => ({ model: {
|
|
118
|
-
...model,
|
|
119
|
-
value,
|
|
120
|
-
velocity: 0,
|
|
121
|
-
running: false
|
|
122
|
-
} })
|
|
123
|
-
}),
|
|
124
|
-
subscriptions: ({ from, to, stiffness, damping }) => Subscription.make()((entry) => ({ ticks: entry({ running: Schema.Boolean }, {
|
|
125
|
-
modelToDependencies: (model) => ({ running: model.running }),
|
|
126
|
-
dependenciesToStream: ({ running }) => {
|
|
127
|
-
if (!running) return Stream.empty;
|
|
128
|
-
const dt = step / 1e3;
|
|
129
|
-
const points = Stream.tick(step).pipe(Stream.scan({
|
|
130
|
-
x: from,
|
|
131
|
-
v: 0
|
|
132
|
-
}, (point) => {
|
|
133
|
-
const force = -stiffness * (point.x - to) - damping * point.v;
|
|
134
|
-
const v = point.v + force * dt;
|
|
135
|
-
return {
|
|
136
|
-
x: point.x + v * dt,
|
|
137
|
-
v
|
|
138
|
-
};
|
|
139
|
-
}), Stream.drop(1));
|
|
140
|
-
return Stream.concat(points.pipe(Stream.takeUntil((point) => settled(point, to)), Stream.map((point) => SpringMessage.Ticked({
|
|
141
|
-
value: point.x,
|
|
142
|
-
velocity: point.v
|
|
143
|
-
}))), Stream.make(SpringMessage.Finished({ value: to })));
|
|
144
|
-
}
|
|
145
|
-
}) }))
|
|
146
|
-
});
|
|
147
|
-
//#endregion
|
|
148
|
-
//#region src/motion/tween.ts
|
|
149
|
-
/**
|
|
150
|
-
* A tween as a bundle: a number animated from one value to another over a
|
|
151
|
-
* duration, in the Model. Progress comes from Effect's clock, so tests drive
|
|
152
|
-
* it with TestClock; the stream ends with Finished, and the value rests at
|
|
153
|
-
* `to`. Linear interpolation only: easing curves stay the application's job.
|
|
154
|
-
*/
|
|
155
|
-
const TweenModel = Schema.Struct({
|
|
156
|
-
value: Schema.Number,
|
|
157
|
-
running: Schema.Boolean
|
|
158
|
-
});
|
|
159
|
-
const TweenMessage = defineMessageUnion({
|
|
160
|
-
Started: {},
|
|
161
|
-
Stopped: {},
|
|
162
|
-
Ticked: { value: Schema.Number },
|
|
163
|
-
/** Carries the exact end value: the stream may stop on either side of it. */
|
|
164
|
-
Finished: { value: Schema.Number }
|
|
165
|
-
});
|
|
166
|
-
const Tween = Bundle.make("Tween", {
|
|
167
|
-
Model: TweenModel,
|
|
168
|
-
Message: TweenMessage,
|
|
169
|
-
args: Schema.Struct({
|
|
170
|
-
from: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
171
|
-
to: Schema.Number.pipe(Schema.check(Schema.isFinite())),
|
|
172
|
-
ms: Schema.Number.pipe(Schema.check(Schema.isGreaterThan(0)), Schema.check(Schema.isFinite()))
|
|
173
|
-
}),
|
|
174
|
-
init: (args) => ({ model: {
|
|
175
|
-
value: args.from,
|
|
176
|
-
running: false
|
|
177
|
-
} }),
|
|
178
|
-
update: (model, message) => TweenMessage.match(message, {
|
|
179
|
-
Started: () => ({ model: {
|
|
180
|
-
...model,
|
|
181
|
-
running: true
|
|
182
|
-
} }),
|
|
183
|
-
Stopped: () => ({ model: {
|
|
184
|
-
...model,
|
|
185
|
-
running: false
|
|
186
|
-
} }),
|
|
187
|
-
Ticked: ({ value }) => ({ model: {
|
|
188
|
-
...model,
|
|
189
|
-
value
|
|
190
|
-
} }),
|
|
191
|
-
Finished: ({ value }) => ({ model: {
|
|
192
|
-
...model,
|
|
193
|
-
value,
|
|
194
|
-
running: false
|
|
195
|
-
} })
|
|
196
|
-
}),
|
|
197
|
-
subscriptions: ({ from, to, ms }) => Subscription.make()((entry) => ({ ticks: entry({ running: Schema.Boolean }, {
|
|
198
|
-
modelToDependencies: (model) => ({ running: model.running }),
|
|
199
|
-
dependenciesToStream: ({ running }) => running ? Stream.unwrap(Clock.currentTimeMillis.pipe(Effect.map((startedAt) => {
|
|
200
|
-
const progress = Stream.tick(16).pipe(Stream.mapEffect(() => Clock.currentTimeMillis), Stream.map((now) => Math.min(1, (now - startedAt) / ms)));
|
|
201
|
-
return Stream.concat(progress.pipe(Stream.takeUntil((done) => done >= 1), Stream.map((done) => TweenMessage.Ticked({ value: from + (to - from) * done }))), Stream.make(TweenMessage.Finished({ value: to })));
|
|
202
|
-
}))) : Stream.empty
|
|
203
|
-
}) }))
|
|
204
|
-
});
|
|
205
|
-
//#endregion
|
|
206
|
-
export { Presence, PresenceMessage, PresenceModel, Spring, SpringMessage, SpringModel, Tween, TweenMessage, TweenModel, isVisible };
|
|
207
|
-
|
|
208
|
-
//# sourceMappingURL=index.mjs.map
|
|
1
|
+
import { a as SpringMessage, c as PresenceMessage, d as motion_exports, i as Spring, l as PresenceModel, n as TweenMessage, o as SpringModel, r as TweenModel, s as Presence, t as Tween, u as isVisible } from "../motion-LfJYAMnP.mjs";
|
|
2
|
+
export { motion_exports as Motion, Presence, PresenceMessage, PresenceModel, Spring, SpringMessage, SpringModel, Tween, TweenMessage, TweenModel, isVisible };
|