{"version":3,"file":"list.cjs","names":[],"sources":["../src/content/list/list.ts"],"sourcesContent":["import { bind, createContext, define, getHost, html, onCleanup, prop, provide, useEmit } from '@vielzeug/ore';\nimport { computed, type Readable, signal, watch } from '@vielzeug/ripple';\nimport { createListControl, lifecycleSignal } from '../../core';\nimport { disablableBundle, LIST_SIZE_PRESET, sizableBundle } from '../../shared';\nimport { sizeVariantMixin } from '../../styles';\nimport type { ComponentSize } from '../../types';\nimport componentStyles from './list.css?inline';\n\n/** Visual variant for `ore-list`. */\nexport type ListVariant = 'bordered' | 'plain' | 'separated';\n\n/** Context provided by `ore-list` to its `ore-list-item` children. */\nexport type ListContext = {\n  /**\n   * Closes every other item's swipe-revealed action panel. Called directly by an item the moment\n   * its own `revealed` attribute transitions to non-null (gesture or direct attribute set) —\n   * replaces listening for a bubbled `reveal` event and re-deriving \"is this actually my direct\n   * child\" with a plain function call, the same shape as `select` below.\n   */\n  requestReveal: (item: HTMLElement) => void;\n  /**\n   * Selects `value` (or clears the selection when `undefined`) and fires `change`. Called\n   * directly by an activated item — mirrors `ore-radio-group`'s `RadioGroupContext.select()`.\n   * No sibling bookkeeping needed here: every item's `selected` is *derived* from `value`, so\n   * changing it here is the only thing that has to happen.\n   */\n  select: (value: string | undefined) => void;\n  selectable: Readable<boolean>;\n  /**\n   * Single source of truth for the current selection — `ore-list-item` never owns its own\n   * \"selected\" state; it's always derived by comparing its own `value` against this one (the\n   * same shape as `ore-radio-group`'s `RadioGroupContext.value`).\n   */\n  value: Readable<string | undefined>;\n};\n/** Injection key for the list context. */\nexport const LIST_CTX = createContext<ListContext>('ListContext');\n\n/** Events emitted by the list component */\nexport type OreListEvents = {\n  /** Emitted when the selected value changes (only when `selectable` is set). */\n  change: { value: null | string };\n};\n\n/** List component properties */\nexport type OreListProps = {\n  /** Disable the entire list — blocks pointer interaction and removes items from tab order */\n  disabled?: boolean;\n  /**\n   * Enables single-selection listbox behavior: clicking (or pressing Enter/Space on) an item\n   * selects it and deselects any previously-selected sibling. Arrow keys / Home / End move\n   * focus between items. Omit for a plain, non-interactive display list.\n   */\n  selectable?: boolean;\n  /** Size applied to all items (propagated via inherited CSS custom properties) */\n  size?: ComponentSize;\n  /**\n   * Selected item's `value` (only meaningful when `selectable`) — the single source of truth for\n   * selection. `ore-list-item` never owns its own selected state; set this directly, bind it for\n   * two-way control, or read it back from `change`.\n   */\n  value?: string;\n  /** Visual variant: 'plain' (default, no dividers) | 'bordered' (outer border + row dividers) | 'separated' (each item is its own card) */\n  variant?: ListVariant;\n};\n\n/**\n * A vertical list container for `ore-list-item` children — plain display list by default, or a\n * keyboard-navigable single-select listbox via `selectable`. Each item can independently reveal\n * a left/right action panel via touch/pointer swipe (see `ore-list-item`'s `actions-left`/\n * `actions-right` slots) — useful for mobile-style row actions (archive, delete, …).\n *\n * @element ore-list\n * @element ore-list-item - Child element for each row\n *\n * @attr {boolean} disabled - Disable the entire list\n * @attr {boolean} selectable - Enable single-selection listbox behavior with arrow-key navigation\n * @attr {string} size - Size applied to all items: 'sm' | 'md' | 'lg'\n * @attr {string} value - Selected item's value (only meaningful when `selectable`) — the single source of truth for selection\n * @attr {string} variant - Visual variant: 'plain' | 'bordered' | 'separated'\n *\n * @fires change - Emitted when the selected value changes. detail: { value: string | null }\n *\n * @slot - `ore-list-item` elements\n *\n * @cssprop --list-radius - Border radius for the 'bordered'/'separated' variants\n *\n * @example\n * ```html\n * <ore-list variant=\"bordered\">\n *   <ore-list-item>Inbox</ore-list-item>\n *   <ore-list-item>Drafts</ore-list-item>\n * </ore-list>\n * <ore-list selectable value=\"inbox\">\n *   <ore-list-item value=\"inbox\">Inbox</ore-list-item>\n *   <ore-list-item value=\"drafts\">Drafts</ore-list-item>\n * </ore-list>\n * ```\n */\nexport const LIST_TAG = 'ore-list' as const;\ndefine<OreListProps>(LIST_TAG, {\n  props: {\n    ...disablableBundle,\n    ...sizableBundle,\n    selectable: prop.bool(false),\n    value: prop.string(),\n    variant: prop.string<ListVariant>(),\n  },\n\n  setup(props) {\n    const el = getHost();\n    const emit = useEmit<OreListEvents>();\n\n    const getItems = (): HTMLElement[] => [\n      ...el.querySelectorAll<HTMLElement>(':scope > ore-list-item:not([disabled])'),\n    ];\n\n    const getAllItems = (): HTMLElement[] => [...el.querySelectorAll<HTMLElement>(':scope > ore-list-item')];\n\n    // Reaches into each item's shadow root for its focusable row — mirrors `ore-accordion`'s\n    // `getSummaryElements()` — rather than relying on `item.focus()` + `shadow: { delegatesFocus:\n    // true }` to land focus correctly, since that cross-boundary reporting is inconsistent enough\n    // between real browsers and jsdom that other multi-item components in this package\n    // (`ore-menu`, `ore-tabs`) defensively check both forms too. Queried via `[part=\"row\"]`, not\n    // the `.row` class — `part` is list-item's declared public seam for exactly this kind of\n    // cross-component reach; the class name is a private styling detail that could change.\n    const getRow = (item: Element): HTMLElement | null =>\n      item.shadowRoot?.querySelector<HTMLElement>('[part=\"row\"]') ?? null;\n\n    const getRows = (): HTMLElement[] =>\n      getItems()\n        .map(getRow)\n        .filter((row): row is HTMLElement => row != null);\n\n    const listControl = createListControl<HTMLElement>({\n      getItems,\n      loop: true,\n      onNavigate: ({ item }) => {\n        getRow(item)?.focus();\n      },\n      signal: lifecycleSignal(onCleanup),\n    });\n\n    // Selection — `selectedValue` is the single source of truth (mirrors `ore-radio-group`'s\n    // `selectedValue`/`RadioGroupContext.value` pattern): reflected onto the `value` attribute,\n    // synced back in when set externally, and read by every item via context to derive its own\n    // `selected` state. No sibling-clearing needed anywhere — changing this one signal is enough.\n    const selectedValue = signal<string | undefined>(props.value.value);\n\n    watch(props.value, (value) => {\n      selectedValue.value = value;\n    });\n\n    const select = (value: string | undefined): void => {\n      if (selectedValue.value === value) return;\n\n      selectedValue.value = value;\n      emit('change', { value: value ?? null });\n    };\n\n    // Only one item's swipe-revealed action panel is open at a time. The item calls this\n    // directly the moment its own `revealed` attribute transitions to non-null — see\n    // `ListContext.requestReveal`'s doc comment for why this replaced a bubbled event.\n    const requestReveal = (item: HTMLElement): void => {\n      for (const sibling of getAllItems()) {\n        if (sibling !== item) sibling.removeAttribute('revealed');\n      }\n    };\n\n    // Closes any swipe-revealed item when the user interacts anywhere outside of it — matches\n    // the outside-pointerdown-closes pattern used by ore-date-picker/ore-time-picker's popovers.\n    const handleOutsidePointerDown = (event: PointerEvent): void => {\n      const open = getAllItems().find((item) => item.hasAttribute('revealed'));\n\n      if (!open || event.composedPath().includes(open)) return;\n\n      open.removeAttribute('revealed');\n    };\n\n    document.addEventListener('pointerdown', handleOutsidePointerDown, { capture: true });\n    onCleanup(() => document.removeEventListener('pointerdown', handleOutsidePointerDown, { capture: true }));\n\n    provide(LIST_CTX, {\n      requestReveal,\n      select,\n      selectable: computed(() => Boolean(props.selectable.value)),\n      value: selectedValue,\n    });\n\n    bind({\n      attr: {\n        'aria-disabled': () => (props.disabled.value ? 'true' : null),\n        role: () => (props.selectable.value ? 'listbox' : 'list'),\n        value: () => selectedValue.value ?? null,\n      },\n      on: {\n        keydown: (event: KeyboardEvent) => {\n          if (!props.selectable.value || props.disabled.value) return;\n\n          const row = event\n            .composedPath()\n            .find((node): node is HTMLElement => node instanceof HTMLElement && node.getAttribute('part') === 'row');\n\n          if (!row) return;\n\n          const focused = getRows().indexOf(row);\n\n          if (focused === -1) return;\n\n          listControl.set(focused);\n          listControl.handleKeydown(event);\n        },\n      },\n    });\n\n    return html`\n      <slot></slot>\n    `;\n  },\n\n  styles: [sizeVariantMixin(LIST_SIZE_PRESET), componentStyles],\n});\n"],"mappings":"gWAoCA,IAAa,GAAA,EAAW,EAAA,cAAA,CAA2B,aAAa,EA+DnD,EAAW,YACxB,EAAA,EAAA,OAAA,CAAqB,EAAU,CAC7B,MAAO,CACL,GAAG,EAAA,iBACH,GAAG,EAAA,cACH,WAAY,EAAA,KAAK,KAAK,EAAK,EAC3B,MAAO,EAAA,KAAK,OAAO,EACnB,QAAS,EAAA,KAAK,OAAoB,CACpC,EAEA,MAAM,EAAO,CACX,IAAM,GAAA,EAAK,EAAA,QAAA,CAAQ,EACb,GAAA,EAAO,EAAA,QAAA,CAAuB,EAE9B,MAAgC,CACpC,GAAG,EAAG,iBAA8B,wCAAwC,CAC9E,EAEM,MAAmC,CAAC,GAAG,EAAG,iBAA8B,wBAAwB,CAAC,EASjG,EAAU,GACd,EAAK,YAAY,cAA2B,cAAc,GAAK,KAE3D,MACJ,EAAS,CAAC,CACP,IAAI,CAAM,CAAC,CACX,OAAQ,GAA4B,GAAO,IAAI,EAE9C,EAAc,EAAA,kBAA+B,CACjD,WACA,KAAM,GACN,YAAa,CAAE,UAAW,CACxB,EAAO,CAAI,CAAC,EAAE,MAAM,CACtB,EACA,OAAQ,EAAA,gBAAgB,EAAA,SAAS,CACnC,CAAC,EAMK,GAAA,EAAgB,EAAA,OAAA,CAA2B,EAAM,MAAM,KAAK,GAElE,EAAA,EAAA,MAAA,CAAM,EAAM,MAAQ,GAAU,CAC5B,EAAc,MAAQ,CACxB,CAAC,EAED,IAAM,EAAU,GAAoC,CAC9C,EAAc,QAAU,IAE5B,EAAc,MAAQ,EACtB,EAAK,SAAU,CAAE,MAAO,GAAS,IAAK,CAAC,EACzC,EAKM,EAAiB,GAA4B,CACjD,IAAK,IAAM,KAAW,EAAY,EAC5B,IAAY,GAAM,EAAQ,gBAAgB,UAAU,CAE5D,EAIM,EAA4B,GAA8B,CAC9D,IAAM,EAAO,EAAY,CAAC,CAAC,KAAM,GAAS,EAAK,aAAa,UAAU,CAAC,EAEnE,CAAC,GAAQ,EAAM,aAAa,CAAC,CAAC,SAAS,CAAI,GAE/C,EAAK,gBAAgB,UAAU,CACjC,EAsCA,OApCA,SAAS,iBAAiB,cAAe,EAA0B,CAAE,QAAS,EAAK,CAAC,GACpF,EAAA,EAAA,UAAA,KAAgB,SAAS,oBAAoB,cAAe,EAA0B,CAAE,QAAS,EAAK,CAAC,CAAC,GAExG,EAAA,EAAA,QAAA,CAAQ,EAAU,CAChB,gBACA,SACA,YAAA,EAAY,EAAA,SAAA,KAAe,EAAQ,EAAM,WAAW,KAAM,EAC1D,MAAO,CACT,CAAC,GAED,EAAA,EAAA,KAAA,CAAK,CACH,KAAM,CACJ,oBAAwB,EAAM,SAAS,MAAQ,OAAS,KACxD,SAAa,EAAM,WAAW,MAAQ,UAAY,OAClD,UAAa,EAAc,OAAS,IACtC,EACA,GAAI,CACF,QAAU,GAAyB,CACjC,GAAI,CAAC,EAAM,WAAW,OAAS,EAAM,SAAS,MAAO,OAErD,IAAM,EAAM,EACT,aAAa,CAAC,CACd,KAAM,GAA8B,aAAgB,aAAe,EAAK,aAAa,MAAM,IAAM,KAAK,EAEzG,GAAI,CAAC,EAAK,OAEV,IAAM,EAAU,EAAQ,CAAC,CAAC,QAAQ,CAAG,EAEjC,IAAY,KAEhB,EAAY,IAAI,CAAO,EACvB,EAAY,cAAc,CAAK,EACjC,CACF,CACF,CAAC,EAEM,EAAA,IAAI;;KAGb,EAEA,OAAQ,CAAC,EAAA,iBAAiB,EAAA,gBAAgB,EAAG,EAAA,OAAe,CAC9D,CAAC"}