{"version":3,"file":"list-item.cjs","names":[],"sources":["../src/content/list/list-item.ts"],"sourcesContent":["import {\n  bind,\n  define,\n  getHost,\n  html,\n  inject,\n  onCleanup,\n  onMounted,\n  prop,\n  useEmit,\n  useSlots,\n  watchEffect,\n} from '@vielzeug/ore';\nimport { computed } from '@vielzeug/ripple';\n\nimport { disablableBundle } from '../../shared';\nimport { reducedMotionMixin } from '../../styles';\nimport { createRevealState } from './_list-item-reveal';\nimport { LIST_CTX } from './list';\nimport componentStyles from './list-item.css?inline';\n\n/** Which side's action panel is currently revealed on a list item. */\nexport type ListItemRevealSide = 'left' | 'right';\n\n/** Events emitted by the list-item component */\nexport type OreListItemEvents = {\n  /** Emitted when the revealed action panel closes (gesture, tap, or programmatic). */\n  conceal: { item: HTMLElement };\n  /**\n   * Emitted when a swipe crosses the full-swipe-through distance — the \"swiped all the way\"\n   * confirm gesture. Fired just before the slotted action's own click (if any).\n   */\n  confirm: { item: HTMLElement; side: ListItemRevealSide };\n  /** Emitted when the item becomes deselected. */\n  deselect: { item: HTMLElement; value: null | string };\n  /** Emitted when a swipe/focus reveals an action panel. */\n  reveal: { item: HTMLElement; side: ListItemRevealSide };\n  /** Emitted when the item becomes selected (only meaningful inside a `selectable` `ore-list`). */\n  select: { item: HTMLElement; value: null | string };\n};\n\n/** List-item component properties */\nexport type OreListItemProps = {\n  /** Disable this item — blocks pointer interaction, swipe actions, and selection */\n  disabled?: boolean;\n  /** Which action panel is revealed: 'left' | 'right' (absent = closed). Settable programmatically. */\n  revealed?: ListItemRevealSide;\n  /**\n   * Opaque value compared against the parent `selectable` `ore-list`'s own `value` to derive\n   * `selected` — required for selection to work. There's no separate `selected` prop: the parent\n   * list's `value` is the single source of truth, so this item's selected state can't drift from\n   * it. Also reported in `select`/`deselect`/the parent's `change` event.\n   */\n  value?: string;\n};\n\n/** Maps a reveal side to its slot name — the one place this pairing is spelled out. */\nconst actionSlot = (side: ListItemRevealSide): 'actions-left' | 'actions-right' =>\n  side === 'left' ? 'actions-left' : 'actions-right';\n\n/**\n * A single row inside `ore-list`. Renders `leading`/default (title)/`description`/`trailing`\n * content plus two off-canvas action panels (`actions-left`, `actions-right`) that a horizontal\n * pointer/touch swipe reveals — useful for mobile-style row actions (archive, delete, …). An\n * action panel only opens if it actually has slotted content; swiping the other direction is a\n * no-op. Revealed actions stay reachable from the keyboard (Tab focus on a slotted action button\n * reveals its panel via `:focus-within`, independent of any gesture).\n *\n * Swiping past the reveal distance snaps the panel fully open on release; swiping further still —\n * past the full-swipe-through distance — auto-confirms that side's action immediately (fires\n * `confirm` and clicks the slot's first element), the same \"swipe all the way\" shortcut as iOS\n * Mail's delete gesture.\n *\n * Closing a revealed panel is deliberately one-directional: tap/click the row itself, tap outside\n * the item, or open a different item's panel. Reverse-swiping an already-open item is not\n * supported — see `ore-list`'s docs for the reasoning.\n *\n * @element ore-list-item\n *\n * @attr {boolean} disabled - Disable this item\n * @attr {boolean} selected - Read-only reflected attribute: `true` when this item's `value` matches the parent `ore-list`'s `value`. Not independently settable.\n * @attr {string}  value    - Opaque value compared against the parent list's `value` to derive `selected`; also reported in select/change events\n * @attr {string}  revealed - Which action panel is revealed: 'left' | 'right'\n *\n * @fires select   - Item becomes selected. detail: { item: HTMLElement, value: string | null }\n * @fires deselect - Item becomes deselected. detail: { item: HTMLElement, value: string | null }\n * @fires reveal   - An action panel opens. detail: { item: HTMLElement, side: 'left' | 'right' }\n * @fires conceal  - The revealed action panel closes. detail: { item: HTMLElement }\n * @fires confirm  - Swiped all the way through — fires before the slotted action's own click. detail: { item: HTMLElement, side: 'left' | 'right' }\n *\n * @slot leading - Content before the title (e.g. icon, avatar)\n * @slot - Item title\n * @slot description - Secondary line below the title\n * @slot trailing - Content after the title (e.g. meta text, chevron)\n * @slot actions-left - Buttons revealed by swiping right (or focusing into this slot)\n * @slot actions-right - Buttons revealed by swiping left (or focusing into this slot)\n *\n * @cssprop --list-item-actions-width - Width of each action panel (default 6rem)\n * @cssprop --list-item-bg - Row background color\n * @cssprop --list-item-hover-bg - Row background on hover\n * @cssprop --list-item-selected-bg - Row background when selected\n * @part row - The row wrapper (focusable, swipeable)\n * @part leading - The leading slot container\n * @part content - The title + description container\n * @part title - The title slot container\n * @part description - The description slot container\n * @part trailing - The trailing slot container\n * @part actions-start - The left action panel container\n * @part actions-end - The right action panel container\n * @example\n * ```html\n * <ore-list-item>\n *   <ore-icon slot=\"leading\" name=\"mail\"></ore-icon>\n *   Inbox\n *   <span slot=\"trailing\">12</span>\n * </ore-list-item>\n * <ore-list-item>\n *   Newsletter\n *   <span slot=\"description\">From Acme Inc.</span>\n *   <ore-button slot=\"actions-right\" color=\"error\">Delete</ore-button>\n * </ore-list-item>\n * ```\n */\nexport const LIST_ITEM_TAG = 'ore-list-item' as const;\ndefine<OreListItemProps>(LIST_ITEM_TAG, {\n  props: {\n    ...disablableBundle,\n    revealed: prop.oneOf<ListItemRevealSide | undefined>(['left', 'right'], undefined),\n    value: prop.string(),\n  },\n\n  setup(props) {\n    const el = getHost();\n    const emit = useEmit<OreListItemEvents>();\n    const slots = useSlots<'actions-left' | 'actions-right' | 'description' | 'leading' | 'trailing'>();\n\n    const listCtx = inject(LIST_CTX);\n    const isSelectable = computed(() => Boolean(listCtx?.selectable.value));\n    // Derived, not owned: `ore-list`'s `value` is the single source of truth, so this can never\n    // drift out of sync with a sibling the way an independently-settable `selected` prop could.\n    const isSelected = computed(\n      () => props.value.value != null && listCtx != null && listCtx.value.value === props.value.value,\n    );\n\n    const hasActions = (side: ListItemRevealSide): boolean => slots.has(actionSlot(side)).value;\n\n    onMounted(() => {\n      const row = el.shadowRoot?.querySelector<HTMLElement>('.row');\n\n      if (!row) return;\n\n      const pan = createRevealState(el, row, {\n        disabled: computed(() => props.disabled.value || Boolean(props.revealed.value)),\n        hasActions,\n        // Clicks the slot's own first element (the common case: a single action button) so its own\n        // click handler runs the real action — `confirm` covers slots without a clickable element.\n        onConfirm: (side) => {\n          const target = slots.elements(actionSlot(side)).value[0] as HTMLElement | undefined;\n\n          emit('confirm', { item: el, side });\n          target?.click();\n        },\n      });\n\n      onCleanup(() => pan.dispose());\n    });\n\n    // Reflects the derived `selected` state as a plain boolean attribute — the styling/\n    // `[selected]`-selector hook — the same way `prop.bool()`'s own reflection would, since it\n    // isn't a real settable prop anymore.\n    watchEffect(() => {\n      el.toggleAttribute('selected', isSelected.value);\n    });\n\n    // Emits select/deselect/reveal/conceal for both gesture-driven AND externally-set (attribute,\n    // or the parent list's `value`) state changes — the plain-reflected-prop equivalent of\n    // ore-accordion-item's native <details> `toggle`-event listener (there's no backing native\n    // element here to piggyback on).\n    let lastSelected = isSelected.value;\n\n    watchEffect(() => {\n      const current = isSelected.value;\n\n      if (current === lastSelected) return;\n\n      lastSelected = current;\n      emit(current ? 'select' : 'deselect', { item: el, value: props.value.value ?? null });\n    });\n\n    let lastRevealed = props.revealed.value ?? null;\n\n    watchEffect(() => {\n      const current = props.revealed.value ?? null;\n\n      if (current === lastRevealed) return;\n\n      lastRevealed = current;\n\n      if (current) {\n        // Only one item may have its swipe actions revealed at a time — tell the list directly\n        // instead of dispatching an event for it to listen for.\n        listCtx?.requestReveal(el);\n        emit('reveal', { item: el, side: current });\n      } else {\n        emit('conceal', { item: el });\n      }\n    });\n\n    const handleActivate = (event: Event): void => {\n      if (props.disabled.value) return;\n\n      if (props.revealed.value) {\n        event.preventDefault();\n        event.stopPropagation();\n        el.removeAttribute('revealed');\n\n        return;\n      }\n\n      if (!isSelectable.value) return;\n\n      const value = props.value.value;\n\n      // Re-clicking an already-selected item deselects it; a value is required to select at all\n      // (undefined would otherwise match every valueless item at once).\n      if (value != null) listCtx?.select(isSelected.value ? undefined : value);\n    };\n\n    const handleRowKeydown = (event: KeyboardEvent): void => {\n      if (event.key !== 'Enter' && event.key !== ' ') return;\n\n      if (props.disabled.value || (!props.revealed.value && !isSelectable.value)) return;\n\n      event.preventDefault();\n      handleActivate(event);\n    };\n\n    bind({\n      attr: {\n        'aria-disabled': () => (props.disabled.value ? 'true' : null),\n        'aria-selected': () => (isSelectable.value ? String(isSelected.value) : null),\n        role: () => (isSelectable.value ? 'option' : 'listitem'),\n      },\n    });\n\n    return html`\n      <div\n        class=\"row\"\n        part=\"row\"\n        tabindex=\"${() => (props.disabled.value ? '-1' : '0')}\"\n        @click=\"${handleActivate}\"\n        @keydown=\"${handleRowKeydown}\">\n        <span class=\"leading\" part=\"leading\"><slot name=\"leading\"></slot></span>\n        <span class=\"content\" part=\"content\">\n          <span class=\"title\" part=\"title\"><slot></slot></span>\n          <span class=\"description\" part=\"description\"><slot name=\"description\"></slot></span>\n        </span>\n        <span class=\"trailing\" part=\"trailing\"><slot name=\"trailing\"></slot></span>\n      </div>\n      <span class=\"actions actions-left\" part=\"actions-start\"><slot name=\"actions-left\"></slot></span>\n      <span class=\"actions actions-right\" part=\"actions-end\"><slot name=\"actions-right\"></slot></span>\n    `;\n  },\n\n  shadow: { delegatesFocus: true },\n  styles: [reducedMotionMixin, componentStyles],\n});\n"],"mappings":"wVAyDA,IAAM,EAAc,GAClB,IAAS,OAAS,eAAiB,gBAiExB,EAAgB,iBAC7B,EAAA,EAAA,OAAA,CAAyB,EAAe,CACtC,MAAO,CACL,GAAG,EAAA,iBACH,SAAU,EAAA,KAAK,MAAsC,CAAC,OAAQ,OAAO,EAAG,IAAA,EAAS,EACjF,MAAO,EAAA,KAAK,OAAO,CACrB,EAEA,MAAM,EAAO,CACX,IAAM,GAAA,EAAK,EAAA,QAAA,CAAQ,EACb,GAAA,EAAO,EAAA,QAAA,CAA2B,EAClC,GAAA,EAAQ,EAAA,SAAA,CAAoF,EAE5F,GAAA,EAAU,EAAA,OAAA,CAAO,EAAA,QAAQ,EACzB,GAAA,EAAe,EAAA,SAAA,KAAe,EAAQ,GAAS,WAAW,KAAM,EAGhE,GAAA,EAAa,EAAA,SAAA,KACX,EAAM,MAAM,OAAS,MAAQ,GAAW,MAAQ,EAAQ,MAAM,QAAU,EAAM,MAAM,KAC5F,EAEM,EAAc,GAAsC,EAAM,IAAI,EAAW,CAAI,CAAC,CAAC,CAAC,OAEtF,EAAA,EAAA,UAAA,KAAgB,CACd,IAAM,EAAM,EAAG,YAAY,cAA2B,MAAM,EAE5D,GAAI,CAAC,EAAK,OAEV,IAAM,EAAM,EAAA,kBAAkB,EAAI,EAAK,CACrC,UAAA,EAAU,EAAA,SAAA,KAAe,EAAM,SAAS,OAAS,EAAQ,EAAM,SAAS,KAAM,EAC9E,aAGA,UAAY,GAAS,CACnB,IAAM,EAAS,EAAM,SAAS,EAAW,CAAI,CAAC,CAAC,CAAC,MAAM,GAEtD,EAAK,UAAW,CAAE,KAAM,EAAI,MAAK,CAAC,EAClC,GAAQ,MAAM,CAChB,CACF,CAAC,GAED,EAAA,EAAA,UAAA,KAAgB,EAAI,QAAQ,CAAC,CAC/B,CAAC,GAKD,EAAA,EAAA,YAAA,KAAkB,CAChB,EAAG,gBAAgB,WAAY,EAAW,KAAK,CACjD,CAAC,EAMD,IAAI,EAAe,EAAW,OAE9B,EAAA,EAAA,YAAA,KAAkB,CAChB,IAAM,EAAU,EAAW,MAEvB,IAAY,IAEhB,EAAe,EACf,EAAK,EAAU,SAAW,WAAY,CAAE,KAAM,EAAI,MAAO,EAAM,MAAM,OAAS,IAAK,CAAC,EACtF,CAAC,EAED,IAAI,EAAe,EAAM,SAAS,OAAS,MAE3C,EAAA,EAAA,YAAA,KAAkB,CAChB,IAAM,EAAU,EAAM,SAAS,OAAS,KAEpC,IAAY,IAEhB,EAAe,EAEX,GAGF,GAAS,cAAc,CAAE,EACzB,EAAK,SAAU,CAAE,KAAM,EAAI,KAAM,CAAQ,CAAC,GAE1C,EAAK,UAAW,CAAE,KAAM,CAAG,CAAC,EAEhC,CAAC,EAED,IAAM,EAAkB,GAAuB,CAC7C,GAAI,EAAM,SAAS,MAAO,OAE1B,GAAI,EAAM,SAAS,MAAO,CACxB,EAAM,eAAe,EACrB,EAAM,gBAAgB,EACtB,EAAG,gBAAgB,UAAU,EAE7B,MACF,CAEA,GAAI,CAAC,EAAa,MAAO,OAEzB,IAAM,EAAQ,EAAM,MAAM,MAItB,GAAS,MAAM,GAAS,OAAO,EAAW,MAAQ,IAAA,GAAY,CAAK,CACzE,EAmBA,OARA,EAAA,EAAA,KAAA,CAAK,CACH,KAAM,CACJ,oBAAwB,EAAM,SAAS,MAAQ,OAAS,KACxD,oBAAwB,EAAa,MAAQ,OAAO,EAAW,KAAK,EAAI,KACxE,SAAa,EAAa,MAAQ,SAAW,UAC/C,CACF,CAAC,EAEM,EAAA,IAAI;;;;wBAIY,EAAM,SAAS,MAAQ,KAAO,IAAK;kBAC5C,EAAe;oBAtBH,GAA+B,EACnD,EAAM,MAAQ,SAAW,EAAM,MAAQ,OAEvC,EAAM,SAAS,OAAU,CAAC,EAAM,SAAS,OAAS,CAAC,EAAa,QAEpE,EAAM,eAAe,EACrB,EAAe,CAAK,GACtB,EAgBiC;;;;;;;;;;KAWnC,EAEA,OAAQ,CAAE,eAAgB,EAAK,EAC/B,OAAQ,CAAC,EAAA,mBAAoB,EAAA,OAAe,CAC9C,CAAC"}