{"version":3,"file":"datagrid.cjs","names":[],"sources":["../src/inputs/datagrid/datagrid.ts"],"sourcesContent":["import { debounce } from '@vielzeug/arsenal/function';\nimport { define, getHost, html, onCleanup, onMounted, prop, unsafeHtml, useEmit, useSlots } from '@vielzeug/ore';\nimport { computed, signal, watch } from '@vielzeug/ripple';\n\nimport { warn } from '../../_dev';\nimport '../../content/icon/icon';\nimport '../../inputs/button/button';\nimport '../../inputs/checkbox/checkbox';\nimport '../../inputs/combobox/combobox';\nimport '../../inputs/select/select';\nimport '../../overlay/popover/popover';\nimport { disablableBundle, loadableBundle } from '../../shared';\nimport { tableBaseMixin } from '../../styles';\nimport componentStyles from './datagrid.css?inline';\nimport { COLUMN_OBSERVED_ATTRS, parseColumnChildren } from './datagrid-column';\nimport {\n  createDataGridModel,\n  type DataGridColumn,\n  type DataGridModel,\n  type DataGridView,\n  type FilterOperator,\n  type FilterOption,\n  type SelectionMode,\n  type SortDirection,\n  type SortState,\n} from './datagrid-model';\nimport { createGridNav, type GridNavHandle } from './datagrid-nav';\n\ntype SortMode = 'client' | 'server';\n\nexport { COLUMN_TAG } from './datagrid-column';\nexport type { DataGridView, FilterOperator, FilterOption } from './datagrid-model';\n\n// ── Pure module-level helpers ─────────────────────────────────────────────────\n\n/**\n * Returns the Lucide icon name for a column's sort state.\n * Pure function — no closure dependency on the grid model.\n */\nexport function sortIconName(state: SortState, key: string): string {\n  if (state.key !== key || state.direction === 'none') return 'chevrons-up-down';\n\n  return state.direction === 'asc' ? 'chevron-up' : 'chevron-down';\n}\n\n/**\n * Returns the WAI-ARIA `aria-sort` value for a column.\n * Pure function — independently unit-testable.\n */\nexport function ariaSortValue(state: SortState, key: string): 'ascending' | 'descending' | 'none' {\n  if (state.key !== key || state.direction === 'none') return 'none';\n\n  return state.direction === 'asc' ? 'ascending' : 'descending';\n}\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\nexport type OreDataGridEvents<T = Record<string, unknown>> = {\n  /** Fired when the user cycles the density via the toolbar button. */\n  'density-change': { density: 'compact' | 'cozy' | 'comfortable' };\n  /** Fired when the active page changes. */\n  'page-change': { pageIndex: number; pageSize: number };\n  /** Fired when a row is expanded or collapsed. */\n  'row-expand': { expanded: boolean; key: string };\n  /** Fired when row selection changes. */\n  'selection-change': { keys: string[]; rows: T[] };\n  /** Fired when the sort column or direction changes. */\n  'sort-change': { direction: SortDirection; key: string };\n  /** Fired when the active view tab changes. detail: { id, label } */\n  'view-change': { id: string; label: string };\n};\n\nexport type OreDataGridProps<T = Record<string, unknown>> = {\n  /**\n   * The ID of the currently active view. Must match an `id` in `views`.\n   * When omitted, no view is active (all data shown).\n   * @example `grid.activeView = 'open'`\n   */\n  activeView?: string;\n  /**\n   * Column definitions (imperative API). Takes precedence over `<ore-column>` children.\n   * Passing `[]` explicitly clears declarative children.\n   * Pass `undefined` (or omit) to use `<ore-column>` children instead.\n   * @example\n   * ```js\n   * grid.columns = [\n   *   { key: 'name', label: 'Name', sortable: true },\n   *   { key: 'email', label: 'Email' },\n   * ];\n   * ```\n   */\n  columns?: DataGridColumn<T>[];\n  /** Cell density: `'compact'` | `'cozy'` (default) | `'comfortable'` */\n  density?: 'compact' | 'cozy' | 'comfortable';\n  /** Disable all interaction. */\n  disabled?: boolean;\n  /** Text shown when there are no rows. */\n  emptyText?: string;\n  /**\n   * Enable row expansion. When set, each row gets a toggle button.\n   * Requires at least one column to have a `renderExpanded` function.\n   *\n   * @security The `renderExpanded` callback's returned HTML string is inserted through\n   * Ore's `unsafeHtml()` directive. If data originates from untrusted user input, sanitize\n   * it before returning (for example, with DOMPurify or your CSP-compliant sanitizer).\n   */\n  expandable?: boolean;\n  /**\n   * Pre-defined filter option definitions per column key.\n   * When provided, these options replace the auto-derived ones in the Filter popover.\n   * @example\n   * ```js\n   * grid.filterOptions = [\n   *   { key: 'role', label: 'Role', options: [{ value: 'Admin' }, { value: 'Editor' }] },\n   * ];\n   * ```\n   */\n  filterOptions?: FilterOption[];\n  /** Stretch the grid to fill its container's width. */\n  fullwidth?: boolean;\n  /**\n   * Function that returns a unique string key per row.\n   * Defaults to `(row) => String(row['id'])`.\n   */\n  getRowKey?: (row: T) => string;\n  /** Accessible label for the grid. Recommended for screen readers. */\n  label?: string;\n  /** Show a busy/loading state with reduced opacity. */\n  loading?: boolean;\n  /** Number of rows per page. Defaults to `10`. Set to `0` to disable pagination. */\n  pageSize?: number;\n  /**\n   * Options for the per-page size selector rendered in the footer.\n   * When provided, a `ore-select` is shown next to the pagination controls.\n   * @example `grid.pageSizeOptions = [10, 25, 50, 100]`\n   */\n  pageSizeOptions?: number[];\n  /**\n   * Row data. Pass as a JS property — not serialisable to an HTML attribute.\n   * @example\n   * ```js\n   * grid.rows = [{ id: '1', name: 'Alice', email: 'alice@example.com' }];\n   * ```\n   */\n  rows?: T[];\n  /**\n   * Accessible label for the search toggle button. Supports localization.\n   * Defaults to `'Search'` (open) and `'Close search'` (close).\n   * Pass a tuple `[openLabel, closeLabel]` to override both.\n   * @example `grid.searchLabel = ['Suchen', 'Suche schließen']`\n   */\n  searchLabel?: [open: string, close: string];\n  /** Placeholder text for the inline search input in the controls bar. */\n  searchPlaceholder?: string;\n  /**\n   * Pre-selected row keys. Setting this from outside will update the internal selection.\n   * @example `grid.selectedKeys = ['1', '3']`\n   */\n  selectedKeys?: string[];\n  /** Row selection mode. */\n  selectionMode?: SelectionMode;\n  /**\n   * Whether sorting is client-side (default) or server-side.\n   * When `'server'`, `sort-change` fires but items are not sorted by the control.\n   */\n  sortMode?: SortMode;\n  /**\n   * A reactive data source from `@vielzeug/sourcerer` (or any compatible object).\n   * When set, the source drives row data, pagination, and search — the `rows` prop is ignored.\n   * Client-side sort and filter are bypassed; wire `sort-change` to `source.setQuery()` externally.\n   * @example\n   * ```js\n   * import { createPageSource } from '@vielzeug/sourcerer';\n   * const source = createPageSource({ load: ({ query, signal }) => api.users(query, { signal }) });\n   * grid.source = source;\n   * ```\n   */\n  source?: DataGridSource<T>;\n  /** Apply alternating row backgrounds. */\n  striped?: boolean;\n  /**\n   * Named view definitions for the controls bar tab strip.\n   * Each view is a label displayed as a tab; switching tabs fires `view-change`.\n   * The consumer is responsible for restoring filter/sort state per view.\n   * @example\n   * ```js\n   * grid.views = [\n   *   { id: 'all', label: 'All' },\n   *   { id: 'open', label: 'Open' },\n   *   { id: 'mine', label: 'Mine' },\n   * ];\n   * grid.activeView = 'all';\n   * ```\n   */\n  views?: DataGridView[];\n};\n\n/**\n * An accessible, keyboard-navigable data grid with sorting, pagination,\n * single/multi row selection, inline search, filter, and named views.\n *\n * @element ore-datagrid\n * @element ore-column - Optional declarative column definition child\n *\n * @attr {boolean} disabled - Disable all interaction\n * @attr {boolean} loading - Show busy/loading state\n * @attr {boolean} striped - Apply alternating row backgrounds\n * @attr {boolean} fullwidth - Stretch the grid to fill its container's width\n * @attr {data} search-label - Tuple [openLabel, closeLabel] for the search toggle button\n * @attr {string} search-placeholder - Placeholder for the inline search input\n * @attr {number} page-size - Rows per page (0 = no pagination, default 10)\n * @attr {string} selection-mode - Row selection: 'none' | 'single' | 'multi'\n * @attr {string} sort-mode - Sorting: 'client' (default) | 'server'\n * @attr {string} density - Cell density: compact | cozy (default) | comfortable\n * @attr {string} empty-text - Text shown when there are no rows\n * @attr {string} label - Accessible label for the grid\n * @attr {string} active-view - ID of the currently active view tab\n *\n * @fires selection-change - Fired when row selection changes. detail: { keys: string[], rows: T[] }\n * @fires sort-change - Fired when sort state changes. detail: { key: string, direction: SortDirection }\n * @fires page-change - Fired when page changes. detail: { pageIndex: number, pageSize: number }\n * @fires row-expand - Fired when a row is expanded or collapsed. detail: { expanded: boolean; key: string }\n * @fires view-change - Fired when the active view tab changes. detail: { id: string, label: string }\n *\n * @cssprop --datagrid-bg - Grid background color\n * @cssprop --datagrid-border-color - Grid and cell border color\n * @cssprop --datagrid-radius - Grid border radius\n * @cssprop --datagrid-shadow - Grid box shadow\n * @cssprop --datagrid-header-bg - Column header background\n * @cssprop --datagrid-row-hover-bg - Row hover background\n * @cssprop --datagrid-row-selected-bg - Selected row background\n * @cssprop --datagrid-stripe-bg - Even-row stripe background\n * @cssprop --datagrid-cell-padding-x - Cell horizontal padding\n * @cssprop --datagrid-cell-padding-y - Cell vertical padding\n * @cssprop --datagrid-cell-max-width - Maximum cell content width before truncating\n * @cssprop --datagrid-max-height - Max scrollable height of the table area\n * @cssprop --datagrid-font-size - Base font size for cells\n *\n * @part controls - The controls bar (tabs + action row)\n * @part table - The `<table>` element\n * @part thead - The `<thead>` element\n * @part tbody - The `<tbody>` element\n * @part row - A body `<tr>` element\n * @part cell - A body `<td>` element\n * @part footer - The pagination footer bar\n *\n * @example\n * ```html\n * <ore-datagrid id=\"grid\" label=\"Users\" selection-mode=\"multi\"></ore-datagrid>\n * <script>\n *   const grid = document.getElementById('grid');\n *   grid.columns = [\n *     { key: 'name', label: 'Name', sortable: true },\n *     { key: 'role', label: 'Role' },\n *   ];\n *   grid.rows = [\n *     { id: '1', name: 'Alice', role: 'Admin' },\n *     { id: '2', name: 'Bob',   role: 'Viewer' },\n *   ];\n *   grid.views = [{ id: 'all', label: 'All' }, { id: 'open', label: 'Open' }];\n *   grid.activeView = 'all';\n * </script>\n * ```\n */\n/**\n * Minimal structural interface for a reactive data source accepted by `ore-datagrid`.\n *\n * Any page-shaped `@vielzeug/sourcerer` source satisfies this interface automatically — no direct\n * sourcerer import is required in refine.\n *\n * When `source` is set on the grid:\n * - `rows` prop is ignored; `source.snapshot.data` drives displayed items.\n * - Pagination reads `source.snapshot.pagination`.\n * - Prev/next buttons call `source.page.previous()` / `source.page.next()`.\n * - Search calls `source.setQuery({ search })`.\n * - `source.snapshot.isFetching` contributes to the grid's `aria-busy` state.\n * - Client-side sort and filter are bypassed; wire `sort-change` to `source.setQuery()` externally.\n *\n * @example\n * ```ts\n * import { createPageSource } from '@vielzeug/sourcerer';\n *\n * const source = createPageSource({\n *   load: ({ query, signal }) =>\n *     fetch(`/api/users?page=${query.page}&limit=${query.pageSize}&search=${query.search}`, { signal })\n *       .then(r => r.json()),\n * });\n *\n * const grid = document.querySelector('ore-datagrid');\n * grid.source = source;\n * ```\n */\nexport type DataGridSource<T = Record<string, unknown>> = {\n  readonly page?: {\n    next(): Promise<void> | void;\n    previous(): Promise<void> | void;\n  };\n  setQuery?(changes: { search?: string }): Promise<void> | void;\n  readonly snapshot: {\n    readonly data: readonly T[];\n    readonly error: { message: string } | null;\n    readonly isFetching: boolean;\n    readonly pagination: {\n      readonly count: number;\n      readonly hasNext: boolean;\n      readonly hasPrevious: boolean;\n      readonly index: number;\n      readonly kind: 'page';\n      readonly size: number;\n      readonly total: number;\n    };\n    readonly query: { readonly search?: string };\n  };\n  subscribe(listener: (snapshot: DataGridSource<T>['snapshot']) => void): () => void;\n};\n\nexport const DATAGRID_TAG = 'ore-datagrid' as const;\n\ndefine<OreDataGridProps>(DATAGRID_TAG, {\n  props: {\n    activeView: prop.string(),\n    density: prop.string<'compact' | 'cozy' | 'comfortable'>(),\n    ...disablableBundle,\n    ...loadableBundle,\n    columns: prop.data<DataGridColumn[]>(),\n    emptyText: prop.string('No data'),\n    expandable: prop.bool(false),\n    filterOptions: prop.data<FilterOption[]>(),\n    fullwidth: prop.bool(false),\n    getRowKey: prop.data<(row: Record<string, unknown>) => string>(),\n    label: prop.string(),\n    pageSize: prop.number(10),\n    pageSizeOptions: prop.data<number[]>(),\n    rows: prop.data<Record<string, unknown>[]>(),\n    searchLabel: prop.data<[string, string]>(),\n    searchPlaceholder: prop.string('Search…'),\n    selectedKeys: prop.data<string[]>(),\n    selectionMode: prop.string<SelectionMode>('none'),\n    sortMode: prop.string<SortMode>('client'),\n    source: prop.data<DataGridSource>(),\n    striped: prop.bool(false),\n    views: prop.data<DataGridView[]>(),\n  },\n\n  setup(props) {\n    const el = getHost();\n    const emit = useEmit<OreDataGridEvents>();\n    const slots = useSlots();\n\n    const isDisabled = computed(() => props.disabled.value === true);\n    const selectionMode = computed(() => props.selectionMode.value ?? 'none');\n\n    // ── Row expansion (hoisted — needed by checkOffset + effectiveColCount) ──\n    const expandedKeys = signal(new Set<string>());\n\n    // resolvedColumns is declared further below; this callback is lazy and only\n    // evaluates when .value is first read (after setup completes), so the\n    // forward closure reference is safe at runtime.\n    const hasExpander = computed(\n      () =>\n        props.expandable.value === true && resolvedColumns.value.some((c) => typeof c.renderExpanded === 'function'),\n    );\n\n    const checkOffset = computed(() => (selectionMode.value === 'multi' ? 1 : 0));\n\n    // ── Page size ────────────────────────────────────────────────────────\n    // Signal driven by the `page-size` prop. Stays in sync with prop changes\n    // so consumers can set grid.pageSize = n reactively after mount.\n    // The per-page size selector also writes to this signal directly.\n    const pageSize = signal<number>(props.pageSize.value ?? 10);\n\n    watch(props.pageSize, (n) => {\n      if (n != null) pageSize.value = n;\n    });\n\n    // ── Declarative ore-column children ─────────────────────────────────────\n    // A writable signal holding columns parsed from <ore-column> children.\n    // Updated on mount and whenever children change. The JS `columns` prop\n    // takes precedence when explicitly set (non-empty).\n    const declarativeColumns = signal<DataGridColumn[]>([]);\n\n    onMounted(() => {\n      declarativeColumns.value = parseColumnChildren(el);\n\n      const columnObserver = new MutationObserver(() => {\n        declarativeColumns.value = parseColumnChildren(el);\n      });\n\n      columnObserver.observe(el, {\n        attributeFilter: COLUMN_OBSERVED_ATTRS as unknown as string[],\n        attributes: true,\n        childList: true,\n        subtree: true,\n      });\n\n      return () => columnObserver.disconnect();\n    });\n\n    // Resolved columns: prop wins when explicitly set (even to []); undefined = not set → use declarative children.\n    const resolvedColumns = computed<DataGridColumn[]>(() => {\n      const propCols = props.columns.value;\n\n      return propCols !== undefined ? propCols : declarativeColumns.value;\n    });\n\n    // ── Key resolution ─────────────────────────────────────────────────────────\n    // Reads getRowKey prop dynamically so changes after mount are reflected.\n\n    const resolveKey = (item: Record<string, unknown>): string => {\n      const fn = props.getRowKey.value;\n\n      if (fn) return fn(item);\n\n      const id = item.id;\n\n      if (id == null) {\n        warn('ore-datagrid: row missing `id` — keys will collide. Provide `getRowKey` or add a unique `id` field.');\n\n        return `__missing_${Math.random().toString(36).slice(2)}`;\n      }\n\n      return String(id);\n    };\n\n    // ── Reactive source bridge ────────────────────────────────────────────────\n    // When `source` is provided it drives rows, pagination, and search.\n    // The `rows` prop and client-side filter pipeline are bypassed.\n\n    type SourceMetaState = {\n      error: { message: string } | null;\n      isLoading: boolean;\n      pageCount: number;\n      pageNumber: number;\n      pageSize: number;\n      totalItems: number;\n    };\n\n    const hasSource = computed(() => props.source.value != null);\n    const sourceItems = signal<Record<string, unknown>[]>([]);\n    const sourceMeta = signal<SourceMetaState>({\n      error: null,\n      isLoading: false,\n      pageCount: 1,\n      pageNumber: 1,\n      pageSize: pageSize.value,\n      totalItems: 0,\n    });\n\n    let _sourceUnsub: (() => void) | null = null;\n\n    onCleanup(() => {\n      _sourceUnsub?.();\n      _sourceUnsub = null;\n    });\n\n    watch(\n      props.source,\n      (src) => {\n        _sourceUnsub?.();\n        _sourceUnsub = null;\n\n        if (!src) {\n          sourceItems.value = [];\n          sourceMeta.value = {\n            error: null,\n            isLoading: false,\n            pageCount: 1,\n            pageNumber: 1,\n            pageSize: pageSize.value,\n            totalItems: 0,\n          };\n\n          return;\n        }\n\n        const update = (snapshot: DataGridSource['snapshot'] = src.snapshot): void => {\n          sourceItems.value = snapshot.data as Record<string, unknown>[];\n          sourceMeta.value = {\n            error: snapshot.error,\n            isLoading: snapshot.isFetching,\n            pageCount: snapshot.pagination.count,\n            pageNumber: snapshot.pagination.index,\n            pageSize: snapshot.pagination.size,\n            totalItems: snapshot.pagination.total,\n          };\n        };\n\n        update();\n        _sourceUnsub = src.subscribe(update);\n      },\n      { immediate: true },\n    );\n\n    // ── Feature model ─────────────────────────────────────────────────────────\n    // One model owns every client-side grid transform and interaction state.\n    // Source-backed grids retain source-owned filtering, sorting, and pagination.\n\n    const model: DataGridModel = createDataGridModel({\n      clientSide: computed(() => !hasSource.value),\n      columns: resolvedColumns,\n      filterOptions: props.filterOptions,\n      getRowKey: resolveKey,\n      items: computed<Record<string, unknown>[]>(() =>\n        hasSource.value ? sourceItems.value : (props.rows.value ?? []),\n      ),\n      onSelectionChange: (keys) => {\n        emit('selection-change', { keys: [...keys], rows: model.selectedRows.value });\n      },\n      onSortChange: (sort) => {\n        emit('sort-change', sort);\n      },\n      pageSize: computed(() => (hasSource.value ? 0 : pageSize.value)),\n      selectionMode,\n      sortMode: computed(() => props.sortMode.value ?? 'client'),\n    });\n\n    const debouncedSearch = debounce((query: unknown) => {\n      const source = props.source.value;\n\n      if (source?.setQuery) void source.setQuery({ search: query as string });\n      else model.setSearchQuery(query as string);\n    }, 250);\n\n    // ── Sync external selected-keys prop into the feature model ───────────────\n\n    watch(\n      props.selectedKeys,\n      (keys) => {\n        if (Array.isArray(keys)) model.setSelection(new Set(keys));\n      },\n      { immediate: true },\n    );\n\n    // ── Column resize (F4) ───────────────────────────────────────────────────\n    // Stores user-dragged widths as { key → px } so they survive re-renders.\n    // Only columns with `resizable: true` get a drag handle.\n\n    const colWidths = signal<Record<string, number>>({});\n\n    const createColResizeHandler =\n      (key: string, th: HTMLElement): ((e: PointerEvent) => void) =>\n      (e: PointerEvent): void => {\n        e.preventDefault();\n\n        const startX = e.clientX;\n        const startW = th.getBoundingClientRect().width;\n        // AbortController ensures listeners are removed even if the component\n        // is destroyed mid-drag (e.g. during SPA navigation).\n        const ac = new AbortController();\n        const { signal: sig } = ac;\n\n        window.addEventListener(\n          'pointermove',\n          (mv: PointerEvent) => {\n            colWidths.value = { ...colWidths.value, [key]: Math.max(40, startW + mv.clientX - startX) };\n          },\n          { signal: sig },\n        );\n\n        window.addEventListener('pointerup', () => ac.abort(), { signal: sig });\n      };\n\n    // ── Visible columns (respects hide/show from column menu) ────────────────\n\n    const visibleColumns = model.visibleColumns;\n\n    // ── Cell value helper ────────────────────────────────────────────────────\n\n    const getCellValue = (col: DataGridColumn, item: Record<string, unknown>): string => {\n      if (col.cell) return col.cell(item);\n\n      const v = item[col.key];\n\n      return v == null ? '' : String(v);\n    };\n\n    // ── Pagination handlers ───────────────────────────────────────────────────\n\n    function handlePage(direction: 'next' | 'prev'): void {\n      const src = props.source.value;\n\n      if (src) {\n        if (direction === 'prev') void src.page?.previous();\n        else void src.page?.next();\n\n        return;\n      }\n\n      // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n      direction === 'prev' ? model.prevPage() : model.nextPage();\n      emit('page-change', { pageIndex: model.pageIndex.value, pageSize: pageSize.value });\n    }\n\n    // ── Select-all helpers ────────────────────────────────\n\n    const isSomeSelected = computed(() => {\n      const page = model.currentPageItems.value;\n\n      if (!page.length || model.isAllSelected()) return false;\n\n      return page.some((item) => model.selectedKeys.value.has(resolveKey(item as Record<string, unknown>)));\n    });\n\n    // ── Column count (used in keyboard nav + empty colspan) ───────────────────\n\n    const effectiveColCount = computed(\n      () => visibleColumns.value.length + (selectionMode.value === 'multi' ? 1 : 0) + (hasExpander.value ? 1 : 0),\n    );\n\n    // ── Pagination info text ──────────────────────────────────────────────────\n\n    const paginationEnabled = computed(() => {\n      if (hasSource.value) return sourceMeta.value.pageCount > 1;\n\n      return pageSize.value > 0;\n    });\n\n    const paginationInfo = computed(() => {\n      if (hasSource.value) {\n        const { pageNumber, pageSize: pSize, totalItems } = sourceMeta.value;\n        const safePSize = Math.max(1, pSize);\n\n        if (!paginationEnabled.value) return `${totalItems} row${totalItems !== 1 ? 's' : ''}`;\n\n        if (totalItems === 0) return '0 to 0 of 0';\n\n        const start = (pageNumber - 1) * safePSize + 1;\n        const end = Math.min(start + safePSize - 1, totalItems);\n\n        return `${start} to ${end} of ${totalItems}`;\n      }\n\n      const total = model.totalItems.value;\n\n      if (!paginationEnabled.value) return `${total} row${total !== 1 ? 's' : ''}`;\n\n      if (total === 0) return '0 to 0 of 0';\n\n      const start = model.pageIndex.value * pageSize.value + 1;\n      const end = Math.min(start + pageSize.value - 1, total);\n\n      return `${start} to ${end} of ${total}`;\n    });\n\n    // ── Source-aware loading and pagination helpers ───────────────────────────\n\n    const isLoading = computed(() => props.loading.value === true || (hasSource.value && sourceMeta.value.isLoading));\n\n    const effectiveHasPrev = computed(() =>\n      hasSource.value ? sourceMeta.value.pageNumber > 1 : model.hasPrevPage.value,\n    );\n\n    const effectiveHasNext = computed(() =>\n      hasSource.value ? sourceMeta.value.pageNumber < sourceMeta.value.pageCount : model.hasNextPage.value,\n    );\n\n    const effectivePageLabel = computed(() => {\n      if (hasSource.value) {\n        return sourceMeta.value.totalItems === 0\n          ? '0 / 0'\n          : `${sourceMeta.value.pageNumber} / ${sourceMeta.value.pageCount}`;\n      }\n\n      return `${model.pageIndex.value + 1} / ${model.pageCount.value}`;\n    });\n\n    // ── Density toggle (E5) ──────────────────────────────────────────────────\n    // Local signal so the toolbar button can cycle density without requiring\n    // the consumer to wire up an external prop. Stays in sync with the `density`\n    // prop: prop changes win; button clicks write back to the host attribute so\n    // the CSS layer picks them up immediately.\n\n    type Density = 'compact' | 'cozy' | 'comfortable';\n\n    const DENSITY_CYCLE: Density[] = ['compact', 'cozy', 'comfortable'];\n    const DENSITY_ICONS: Record<Density, string> = {\n      comfortable: 'rows-2',\n      compact: 'rows-4',\n      cozy: 'rows-3',\n    };\n    const DENSITY_LABELS: Record<Density, string> = {\n      comfortable: 'Density: Comfortable',\n      compact: 'Density: Compact',\n      cozy: 'Density: Cozy',\n    };\n\n    const densitySignal = signal<Density>(props.density.value ?? 'cozy');\n\n    watch(props.density, (d) => {\n      if (d) densitySignal.value = d;\n    });\n\n    const cycleDensity = (): void => {\n      const idx = DENSITY_CYCLE.indexOf(densitySignal.value);\n      const next = DENSITY_CYCLE[(idx + 1) % DENSITY_CYCLE.length]!;\n\n      densitySignal.value = next;\n      el.setAttribute('density', next);\n      emit('density-change', { density: next });\n    };\n\n    // ── Filter badge hover state ────────────────────────────────────────────\n    // Drives the dot↔count toggle on the filter toolbar badge: dot at rest,\n    // count on hover/focus so the number is revealed on interaction only.\n    const filterBadgeActive = signal(false);\n\n    // ── Row expansion (toggle handler) ───────────────────────────────────────\n\n    const toggleExpand = (key: string): void => {\n      const next = new Set(expandedKeys.value);\n      const expanded = !next.has(key);\n\n      // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n      expanded ? next.add(key) : next.delete(key);\n      expandedKeys.value = next;\n      emit('row-expand', { expanded, key });\n    };\n\n    // ── Keyboard cell navigation (roving tabindex — extracted to datagrid-nav.ts) ──\n    // navHandle is initialised with a real sentinel signal so the first render\n    // produces correct tabindex values (row=0, col=0 → '0') before onMounted.\n    // onMounted replaces it with the live handle from createGridNav.\n\n    let navHandle: GridNavHandle = {\n      activeCell: signal({ col: 0, row: 0 }),\n      focusCell: () => {},\n    };\n\n    onMounted(() => {\n      const shadow = el.shadowRoot!;\n      const table = shadow.querySelector<HTMLElement>('.dg-table');\n\n      if (!table) return;\n\n      const { cleanup, handle } = createGridNav(table, shadow);\n\n      navHandle = handle;\n\n      return cleanup;\n    });\n\n    // Expose programmatic focusCell API on the host element (F6)\n    (el as HTMLElement & { focusCell: (pos: { col: number; row: number }) => void }).focusCell = (pos) =>\n      navHandle.focusCell(pos);\n\n    // ── Render helpers ────────────────────────────────────────────────────────\n    // Each helper renders one self-contained region, closing only over the\n    // signals it actually needs. This keeps the root template readable.\n\n    const renderViewTabs = (): unknown => {\n      const views = props.views.value ?? [];\n      const activeId = props.activeView.value;\n\n      return html`\n        <div\n          class=\"dg-tabs\"\n          role=\"tablist\"\n          aria-label=\"Views\"\n          aria-controls=\"dg-tabpanel\"\n          @keydown=\"${(e: KeyboardEvent) => {\n            if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) return;\n\n            const tabs = Array.from((e.currentTarget as HTMLElement).querySelectorAll('.dg-tab'));\n            const activeIdx = tabs.findIndex((t) => t.classList.contains('dg-tab--active'));\n\n            let nextIdx = activeIdx;\n\n            if (e.key === 'ArrowLeft') nextIdx = Math.max(0, activeIdx - 1);\n            else if (e.key === 'ArrowRight') nextIdx = Math.min(tabs.length - 1, activeIdx + 1);\n            else if (e.key === 'Home') nextIdx = 0;\n            else if (e.key === 'End') nextIdx = tabs.length - 1;\n\n            if (nextIdx !== activeIdx) {\n              e.preventDefault();\n\n              const nextTab = tabs[nextIdx] as HTMLElement;\n\n              nextTab.focus();\n              nextTab.click();\n            }\n          }}\">\n          ${views.map(\n            (view) => html`\n              <ore-button\n                class=\"${() => `dg-tab${activeId === view.id ? ' dg-tab--active' : ''}`}\"\n                role=\"tab\"\n                variant=\"ghost\"\n                rounded=\"full\"\n                size=\"sm\"\n                tabindex=\"${() => (activeId === view.id ? '0' : '-1')}\"\n                aria-selected=\"${() => String(activeId === view.id)}\"\n                aria-controls=\"dg-tabpanel\"\n                @click=\"${() => {\n                  emit('view-change', { id: view.id, label: view.label });\n                }}\">\n                ${view.label}\n              </ore-button>\n            `,\n          )}\n        </div>\n      `;\n    };\n\n    const renderSortPopover = (): unknown => html`\n      <ore-popover class=\"dg-action-popover\" placement=\"bottom-end\" label=\"Sort\" style=\"--popover-min-width:18rem\">\n        <ore-button variant=\"ghost\" size=\"sm\" icon-only label=\"Sort\">\n          <ore-icon name=\"arrow-up-down\" size=\"15\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n        </ore-button>\n        <div slot=\"content\" class=\"dg-pop-sort\">\n          <div class=\"dg-pop-header\">\n            <span class=\"dg-pop-title\">Sort by</span>\n            <ore-button\n              class=\"dg-icon-btn\"\n              variant=\"ghost\"\n              size=\"sm\"\n              icon-only\n              label=\"Clear sort\"\n              disabled=\"${() => model.sortState.value.direction === 'none' || undefined}\"\n              @click=\"${() => model.sortTo('', 'none')}\">\n              <ore-icon name=\"trash-2\" size=\"14\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n            </ore-button>\n          </div>\n          <div class=\"dg-pop-sort-row\">\n            <ore-select\n              class=\"dg-pop-select\"\n              variant=\"flat\"\n              size=\"sm\"\n              rounded=\"lg\"\n              placeholder=\"Property\"\n              fullwidth\n              value=\"${() => model.sortState.value.key}\"\n              options=\"${() => resolvedColumns.value.map((c) => ({ label: c.label, value: c.key }))}\"\n              @change=\"${(e: CustomEvent<{ values: string[] }>) => {\n                const key = e.detail.values[0] ?? '';\n                const dir = model.sortState.value.direction === 'none' ? 'asc' : model.sortState.value.direction;\n\n                model.sortTo(key, dir);\n              }}\"></ore-select>\n            <ore-select\n              class=\"dg-pop-dir-select\"\n              variant=\"flat\"\n              size=\"sm\"\n              rounded=\"lg\"\n              fullwidth\n              disabled=\"${() => !model.sortState.value.key || undefined}\"\n              value=\"${() => (model.sortState.value.direction === 'none' ? 'asc' : model.sortState.value.direction)}\"\n              options=\"${() => [\n                { label: 'A → Z', value: 'asc' },\n                { label: 'Z → A', value: 'desc' },\n              ]}\"\n              @change=\"${(e: CustomEvent<{ values: string[] }>) => {\n                if (model.sortState.value.key) {\n                  model.sortTo(model.sortState.value.key, (e.detail.values[0] ?? 'asc') as 'asc' | 'desc');\n                }\n              }}\"></ore-select>\n          </div>\n        </div>\n      </ore-popover>\n    `;\n\n    const renderFilterPopover = (): unknown => html`\n      <ore-popover\n        class=\"dg-action-popover\"\n        placement=\"bottom-end\"\n        label=\"Filter\"\n        style=\"--popover-min-width:16rem;--popover-max-height:min(90vh,48rem)\">\n        ${() =>\n          model.filterDefs.value.length\n            ? html`\n                <ore-badge\n                  anchor=\"top-end\"\n                  color=\"primary\"\n                  size=\"xs\"\n                  count=\"${() => (filterBadgeActive.value ? model.filterDefs.value.length : undefined)}\"\n                  dot=\"${() => !filterBadgeActive.value || undefined}\"\n                  label=\"${() =>\n                    `${model.filterDefs.value.length} active filter${model.filterDefs.value.length > 1 ? 's' : ''}`}\"\n                  aria-hidden=\"true\"\n                  @mouseenter=\"${() => (filterBadgeActive.value = true)}\"\n                  @mouseleave=\"${() => (filterBadgeActive.value = false)}\"\n                  @focusin=\"${() => (filterBadgeActive.value = true)}\"\n                  @focusout=\"${() => (filterBadgeActive.value = false)}\">\n                  <ore-button slot=\"target\" variant=\"ghost\" size=\"sm\" icon-only label=\"Filter\">\n                    <ore-icon name=\"filter\" size=\"15\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n                  </ore-button>\n                </ore-badge>\n              `\n            : html`\n                <ore-button class=\"dg-icon-btn\" variant=\"ghost\" size=\"sm\" icon-only label=\"Filter\">\n                  <ore-icon name=\"filter\" size=\"15\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n                </ore-button>\n              `}\n        <div slot=\"content\" class=\"dg-pop-filter\">\n          <div class=\"dg-pop-header\">\n            <span class=\"dg-pop-title\">Filter by</span>\n            ${() =>\n              model.filterDefs.value.length\n                ? html`\n                    <ore-button\n                      class=\"dg-icon-btn\"\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      icon-only\n                      label=\"Clear all filters\"\n                      @click=\"${() => model.clearAllFilters()}\">\n                      <ore-icon name=\"trash-2\" size=\"14\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n                    </ore-button>\n                  `\n                : html``}\n          </div>\n\n          <!-- Always-visible field picker: select columns to add filter rules -->\n          <div class=\"dg-pop-filter-fields\">\n            <ore-combobox\n              placeholder=\"Add filter…\"\n              multiple\n              fullwidth\n              autoclose\n              options=\"${() =>\n                resolvedColumns.value.map((col) => ({\n                  label: col.label,\n                  value: col.key,\n                }))}\"\n              value=\"${() => model.filterDefs.value.map((f) => f.key)}\"\n              @change=\"${(e: CustomEvent<{ values: string[] }>) => {\n                model.setActiveFilterKeys(e.detail.values);\n              }}\"></ore-combobox>\n          </div>\n\n          <!-- Active filter rules (appear below the field picker as rules are added) -->\n          <div class=\"dg-pop-filter-rules\" ?hidden=\"${() => !model.filterDefs.value.length}\">\n            ${() =>\n              model.filterDefs.value.map(\n                (f) => html`\n                  <div class=\"dg-pop-filter-rule\">\n                    <div class=\"dg-pop-filter-rule-header\">\n                      <span class=\"dg-pop-filter-field\">${f.label}</span>\n                      <ore-select\n                        class=\"dg-pop-filter-op-select\"\n                        variant=\"ghost\"\n                        size=\"sm\"\n                        fullwidth\n                        value=\"${() => model.filterValues.value.get(f.key)?.operator ?? 'contains'}\"\n                        options=\"${() => f.operators ?? []}\"\n                        @change=\"${(e: CustomEvent<{ values: string[] }>) => {\n                          model.setFilterOperator(f.key, e.detail.values[0] as FilterOperator);\n                        }}\"></ore-select>\n                      <ore-button\n                        class=\"dg-icon-btn\"\n                        variant=\"ghost\"\n                        size=\"sm\"\n                        icon-only\n                        label=\"Remove filter\"\n                        @click=\"${() => model.removeFilter(f.key)}\">\n                        <ore-icon name=\"trash-2\" size=\"13\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n                      </ore-button>\n                    </div>\n                    <ore-combobox\n                      class=\"dg-filter\"\n                      placeholder=\"${() => f.label}\"\n                      options=\"${() => f.options}\"\n                      value=\"${() => [...(model.filterValues.value.get(f.key)?.values ?? [])]}\"\n                      disabled=\"${() => isDisabled.value || undefined}\"\n                      multiple\n                      fullwidth\n                      autoclose\n                      @change=\"${(e: CustomEvent<{ values: string[] }>) => {\n                        model.setFilter(f.key, e.detail.values);\n                      }}\"></ore-combobox>\n                  </div>\n                `,\n              )}\n          </div>\n        </div>\n      </ore-popover>\n    `;\n\n    const renderColumnMenu = (): unknown => html`\n      <ore-popover\n        class=\"dg-action-popover\"\n        placement=\"bottom-end\"\n        label=\"Column options\"\n        style=\"--popover-min-width:18rem\">\n        <ore-button class=\"dg-icon-btn\" variant=\"ghost\" size=\"sm\" icon-only label=\"Column options\">\n          <ore-icon name=\"columns-2\" size=\"15\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n        </ore-button>\n        <div slot=\"content\" class=\"dg-pop-col-list\" role=\"menu\" aria-label=\"Column visibility\">\n          ${() =>\n            resolvedColumns.value.map(\n              (col) => html`\n                <ore-button\n                  class=\"dg-pop-col-item\"\n                  role=\"menuitemcheckbox\"\n                  variant=\"ghost\"\n                  size=\"sm\"\n                  aria-checked=\"${() => String(!model.hiddenColumns.value.has(col.key))}\"\n                  @click=\"${() => model.toggleColumnVisibility(col.key)}\">\n                  <ore-icon\n                    slot=\"prefix\"\n                    name=\"${() => (model.hiddenColumns.value.has(col.key) ? 'eye-off' : 'eye')}\"\n                    size=\"13\"\n                    stroke-width=\"2\"\n                    aria-hidden=\"true\"></ore-icon>\n                  ${col.label}\n                </ore-button>\n              `,\n            )}\n        </div>\n      </ore-popover>\n    `;\n\n    // ── Template ──────────────────────────────────────────────────────────────\n\n    return html`\n      <!-- ── Controls Bar ────────────────────────────────────────────────── -->\n      <div class=\"dg-controls\" part=\"controls\">\n        <!-- Left: view tabs -->\n        ${() => renderViewTabs()}\n\n        <!-- Right: Action bar -->\n        <div class=\"dg-actions\">\n          ${() => renderSortPopover()} ${() => renderFilterPopover()} ${() => renderColumnMenu()}\n\n          <!-- Density toggle -->\n          <ore-button\n            class=\"dg-icon-btn\"\n            variant=\"ghost\"\n            size=\"sm\"\n            icon-only\n            label=\"${() => DENSITY_LABELS[densitySignal.value]}\"\n            @click=\"${cycleDensity}\">\n            <ore-icon\n              name=\"${() => DENSITY_ICONS[densitySignal.value]}\"\n              size=\"15\"\n              stroke-width=\"1.75\"\n              aria-hidden=\"true\"></ore-icon>\n          </ore-button>\n\n          <span class=\"dg-action-divider\" aria-hidden=\"true\" ?hidden=\"${() => !slots.has('actions').value}\"></span>\n\n          <slot name=\"actions\"></slot>\n\n          <!-- Search toggle: rightmost, expands in-place -->\n          <div class=\"${() => `dg-search${model.searchActive.value ? ' dg-search--open' : ''}`}\">\n            ${() =>\n              model.searchActive.value\n                ? html`\n                    <ore-input\n                      class=\"dg-search-input\"\n                      type=\"search\"\n                      variant=\"flat\"\n                      size=\"sm\"\n                      rounded=\"full\"\n                      placeholder=\"${() => props.searchPlaceholder.value ?? 'Search…'}\"\n                      disabled=\"${() => isDisabled.value || undefined}\"\n                      ref=\"${(inputEl: HTMLElement | null) => {\n                        if (inputEl) requestAnimationFrame(() => (inputEl as HTMLElement).focus());\n                      }}\"\n                      @input=\"${(e: CustomEvent<{ value: string }>) => {\n                        debouncedSearch(e.detail.value);\n                      }}\"\n                      @keydown=\"${(e: KeyboardEvent) => {\n                        if (e.key === 'Escape') {\n                          const src = props.source.value;\n\n                          if (src?.setQuery) void src.setQuery({ search: '' });\n\n                          model.toggleSearch();\n                        }\n                      }}\">\n                      <ore-icon slot=\"prefix\" name=\"search\" size=\"13\" stroke-width=\"1.75\" aria-hidden=\"true\"></ore-icon>\n                    </ore-input>\n                  `\n                : html``}\n            <ore-button\n              variant=\"ghost\"\n              size=\"sm\"\n              icon-only\n              label=\"${() => {\n                const [open, close] = props.searchLabel.value ?? ['Search', 'Close search'];\n\n                return model.searchActive.value ? close : open;\n              }}\"\n              @click=\"${() => {\n                const src = props.source.value;\n\n                if (src?.setQuery && model.searchActive.value) void src.setQuery({ search: '' });\n\n                model.toggleSearch();\n              }}\">\n              <ore-icon\n                name=\"${() => (model.searchActive.value ? 'x' : 'search')}\"\n                size=\"15\"\n                stroke-width=\"1.75\"\n                aria-hidden=\"true\"></ore-icon>\n            </ore-button>\n          </div>\n        </div>\n      </div>\n\n      <div id=\"dg-tabpanel\" class=\"dg-scroll\" role=\"tabpanel\" aria-label=\"Data\">\n        <div class=\"dg-loading-overlay\">\n          <ore-icon name=\"loader-2\" size=\"24\" class=\"dg-spin\" aria-hidden=\"true\"></ore-icon>\n        </div>\n        <table\n          class=\"dg-table\"\n          part=\"table\"\n          role=\"grid\"\n          aria-label=\"${() => props.label.value ?? undefined}\"\n          aria-busy=\"${() => (isLoading.value ? 'true' : null)}\"\n          aria-disabled=\"${() => (isDisabled.value ? 'true' : null)}\">\n          <!-- Head -->\n          <thead class=\"dg-head\" part=\"thead\">\n            <tr role=\"row\">\n              ${() =>\n                selectionMode.value === 'multi'\n                  ? html`\n                      <th\n                        class=\"dg-th dg-th-check\"\n                        role=\"columnheader\"\n                        scope=\"col\"\n                        tabindex=\"${() =>\n                          navHandle.activeCell.value.row === 0 &&\n                          navHandle.activeCell.value.col === 0 &&\n                          checkOffset.value >= 1\n                            ? '0'\n                            : '-1'}\">\n                        <ore-checkbox\n                          class=\"dg-check\"\n                          checked=\"${() => model.isAllSelected()}\"\n                          indeterminate=\"${isSomeSelected}\"\n                          ?disabled=\"${isDisabled}\"\n                          aria-label=\"Select all rows on this page\"\n                          @change=\"${() => {\n                            if (!isDisabled.value) model.selectAll();\n                          }}\"></ore-checkbox>\n                      </th>\n                    `\n                  : html``}\n              ${() =>\n                visibleColumns.value.map((col: DataGridColumn, colIdx: number) => {\n                  const isLast = colIdx === visibleColumns.value.length - 1;\n\n                  return html`\n                    <th\n                      class=\"${`dg-th${isLast && hasExpander.value ? ' dg-th-last' : ''}`}\"\n                      role=\"columnheader\"\n                      scope=\"col\"\n                      tabindex=\"${() => {\n                        const ac = navHandle.activeCell.value;\n\n                        return ac.row === 0 && ac.col === colIdx + checkOffset.value ? '0' : '-1';\n                      }}\"\n                      data-align=\"${col.align ?? 'left'}\"\n                      aria-sort=\"${() => (col.sortable ? ariaSortValue(model.sortState.value, col.key) : undefined)}\"\n                      aria-label=\"${col.sortable ? undefined : (col.headerLabel ?? col.label)}\"\n                      style=\"${() => {\n                        const dragged = colWidths.value[col.key];\n\n                        return dragged ? `width:${dragged}px` : col.width ? `width:${col.width}` : '';\n                      }}\">\n                      <div class=\"dg-th-inner\">\n                        ${() =>\n                          col.sortable\n                            ? html`\n                                <ore-button\n                                  class=\"dg-sort-btn\"\n                                  variant=\"text\"\n                                  size=\"sm\"\n                                  fullwidth\n                                  label=\"${col.headerLabel ?? col.label}\"\n                                  disabled=\"${() => isDisabled.value || undefined}\"\n                                  @click=\"${() => {\n                                    if (!isDisabled.value) model.sortBy(col.key);\n                                  }}\">\n                                  ${col.label}\n                                  <ore-icon\n                                    slot=\"suffix\"\n                                    class=\"dg-sort-icon\"\n                                    name=\"${() => sortIconName(model.sortState.value, col.key)}\"\n                                    size=\"14\"\n                                    stroke-width=\"2\"></ore-icon>\n                                </ore-button>\n                              `\n                            : col.label}\n                        ${\n                          col.resizable\n                            ? html`\n                                <span\n                                  class=\"dg-col-resize\"\n                                  aria-hidden=\"true\"\n                                  ref=\"${(handleEl: HTMLElement | null) => {\n                                    if (!handleEl) return;\n\n                                    const th = handleEl.closest('th') as HTMLElement | null;\n\n                                    if (th)\n                                      handleEl.addEventListener('pointerdown', createColResizeHandler(col.key, th));\n                                  }}\"></span>\n                              `\n                            : html``\n                        }\n                      </div>\n                    </th>\n                  `;\n                })}\n              ${() =>\n                hasExpander.value\n                  ? html`\n                      <th\n                        class=\"dg-th dg-th-expand\"\n                        role=\"columnheader\"\n                        scope=\"col\"\n                        aria-label=\"Row details\"\n                        tabindex=\"-1\"></th>\n                    `\n                  : html``}\n            </tr>\n          </thead>\n\n          <!-- Body -->\n          <tbody class=\"dg-body\" part=\"tbody\">\n            ${() =>\n              model.currentPageItems.value.length === 0\n                ? html`\n                    <tr role=\"row\">\n                      <td class=\"dg-empty\" role=\"gridcell\" colspan=\"${() => String(effectiveColCount.value)}\">\n                        <div class=\"dg-empty-content\">\n                          ${() => props.emptyText.value ?? 'No data'}\n                          ${() =>\n                            model.searchQuery.value || model.filterValues.value.size\n                              ? html`\n                                  <div class=\"dg-empty-actions\">\n                                    <ore-button\n                                      variant=\"text\"\n                                      size=\"sm\"\n                                      @click=\"${() => {\n                                        model.resetSearch();\n                                        model.resetFilters();\n                                      }}\">\n                                      Clear all filters & search\n                                    </ore-button>\n                                  </div>\n                                `\n                              : html``}\n                        </div>\n                      </td>\n                    </tr>\n                  `\n                : model.currentPageItems.value.map((item: Record<string, unknown>, itemIdx: number) => {\n                    const key = resolveKey(item);\n                    const isSelectable = selectionMode.value !== 'none' && !isDisabled.value;\n                    const rowIdx = itemIdx + 1;\n\n                    return html`\n                      <tr\n                        class=\"dg-tr\"\n                        part=\"row\"\n                        role=\"row\"\n                        aria-selected=\"${() =>\n                          selectionMode.value !== 'none' ? String(model.selectedKeys.value.has(key)) : null}\"\n                        aria-expanded=\"${() => (hasExpander.value ? String(expandedKeys.value.has(key)) : null)}\"\n                        ?data-selectable=\"${isSelectable}\"\n                        ?data-disabled=\"${isDisabled}\"\n                        @click=\"${() => {\n                          if (isSelectable && selectionMode.value === 'single') model.toggleRow(key);\n                        }}\"\n                        @keydown=\"${(e: KeyboardEvent) => {\n                          if (\n                            (e.key === 'Enter' || e.key === ' ') &&\n                            isSelectable &&\n                            selectionMode.value === 'single'\n                          ) {\n                            e.preventDefault();\n                            model.toggleRow(key);\n                          }\n                        }}\">\n                        ${() =>\n                          selectionMode.value === 'multi'\n                            ? html`\n                                <td\n                                  class=\"dg-td dg-td-check\"\n                                  role=\"gridcell\"\n                                  tabindex=\"${() => {\n                                    const ac = navHandle.activeCell.value;\n\n                                    return ac.row === rowIdx && ac.col === 0 ? '0' : '-1';\n                                  }}\"\n                                  @keydown=\"${(e: KeyboardEvent) => {\n                                    if (e.key === 'Enter' || e.key === ' ') {\n                                      e.preventDefault();\n\n                                      if (!isDisabled.value) model.toggleRow(key);\n                                    }\n                                  }}\">\n                                  <ore-checkbox\n                                    class=\"dg-check\"\n                                    checked=\"${() => model.selectedKeys.value.has(key)}\"\n                                    ?disabled=\"${isDisabled}\"\n                                    aria-label=\"Select row\"\n                                    tabindex=\"-1\"\n                                    @click=\"${(e: MouseEvent) => e.stopPropagation()}\"\n                                    @change=\"${() => {\n                                      if (!isDisabled.value) model.toggleRow(key);\n                                    }}\"></ore-checkbox>\n                                </td>\n                              `\n                            : html``}\n                        ${visibleColumns.value.map((col: DataGridColumn, colIdx: number) => {\n                          const value = getCellValue(col, item as Record<string, unknown>);\n\n                          const isLastCol = colIdx === visibleColumns.value.length - 1;\n\n                          return html`\n                            <td\n                              class=\"${`dg-td${isLastCol && hasExpander.value ? ' dg-td-last' : ''}`}\"\n                              part=\"cell\"\n                              role=\"gridcell\"\n                              data-align=\"${col.align ?? 'left'}\"\n                              tabindex=\"${() => {\n                                const ac = navHandle.activeCell.value;\n\n                                return ac.row === rowIdx && ac.col === colIdx + checkOffset.value ? '0' : '-1';\n                              }}\"\n                              title=\"${value}\">\n                              ${value}\n                            </td>\n                          `;\n                        })}\n                        ${() =>\n                          hasExpander.value\n                            ? html`\n                                <td\n                                  class=\"dg-td dg-td-expand\"\n                                  role=\"gridcell\"\n                                  tabindex=\"${() => {\n                                    const ac = navHandle.activeCell.value;\n\n                                    return ac.row === rowIdx && ac.col === effectiveColCount.value - 1 ? '0' : '-1';\n                                  }}\"\n                                  @keydown=\"${(e: KeyboardEvent) => {\n                                    if (e.key === 'Enter' || e.key === ' ') {\n                                      e.preventDefault();\n\n                                      if (!isDisabled.value) toggleExpand(key);\n                                    }\n                                  }}\">\n                                  <ore-button\n                                    class=\"dg-expand-btn\"\n                                    variant=\"ghost\"\n                                    size=\"sm\"\n                                    icon-only\n                                    tabindex=\"-1\"\n                                    label=\"${() => (expandedKeys.value.has(key) ? 'Collapse row' : 'Expand row')}\"\n                                    aria-expanded=\"${() => String(expandedKeys.value.has(key))}\"\n                                    disabled=\"${() => isDisabled.value || undefined}\"\n                                    @click=\"${(e: MouseEvent) => {\n                                      e.stopPropagation();\n\n                                      if (!isDisabled.value) toggleExpand(key);\n                                    }}\">\n                                    <ore-icon\n                                      name=\"${() => (expandedKeys.value.has(key) ? 'chevron-up' : 'chevron-down')}\"\n                                      size=\"14\"\n                                      stroke-width=\"2\"\n                                      aria-hidden=\"true\"></ore-icon>\n                                  </ore-button>\n                                </td>\n                              `\n                            : html``}\n                      </tr>\n                      ${() =>\n                        hasExpander.value && expandedKeys.value.has(key)\n                          ? html`\n                              <tr class=\"dg-tr-expanded\" role=\"row\">\n                                <td\n                                  class=\"dg-td-expanded\"\n                                  role=\"gridcell\"\n                                  colspan=\"${() => String(effectiveColCount.value)}\">\n                                  ${\n                                    // This deliberately marks the HTML sink. Callers must\n                                    // sanitize untrusted content inside renderExpanded().\n                                    unsafeHtml(() => {\n                                      const renderer = resolvedColumns.value.find(\n                                        (c) => typeof c.renderExpanded === 'function',\n                                      );\n\n                                      return renderer?.renderExpanded?.(item) ?? '';\n                                    })\n                                  }\n                                </td>\n                              </tr>\n                            `\n                          : html``}\n                    `;\n                  })}\n          </tbody>\n        </table>\n      </div>\n\n      <!-- Footer / Pagination -->\n      ${() =>\n        paginationEnabled.value\n          ? html`\n              <div class=\"dg-footer\" part=\"footer\" role=\"navigation\" aria-label=\"Pagination\">\n                ${() =>\n                  !(props.pageSizeOptions.value ?? []).length\n                    ? html`\n                        <span class=\"dg-footer-info\" dir=\"ltr\" aria-live=\"polite\" aria-atomic=\"true\">\n                          ${paginationInfo}\n                        </span>\n                      `\n                    : html``}\n                <div class=\"dg-footer-end\">\n                  ${() => {\n                    const opts = props.pageSizeOptions.value ?? [];\n\n                    return opts.length\n                      ? html`\n                          <div class=\"dg-page-size-wrap\">\n                            <ore-select\n                              class=\"dg-page-size-select\"\n                              fullwidth\n                              aria-label=\"Rows per page\"\n                              value=\"${() => String(pageSize.value)}\"\n                              options=\"${() => opts.map((n) => ({ label: String(n), value: String(n) }))}\"\n                              disabled=\"${() => isDisabled.value || undefined}\"\n                              @change=\"${(e: CustomEvent<{ values: string[] }>) => {\n                                const n = parseInt(e.detail.values[0], 10);\n\n                                if (!Number.isNaN(n)) {\n                                  pageSize.value = n;\n                                  model.goToPage(0);\n                                  emit('page-change', { pageIndex: 0, pageSize: n });\n                                }\n                              }}\"></ore-select>\n                          </div>\n                        `\n                      : html``;\n                  }}\n                  <div class=\"dg-pagination\" role=\"group\" aria-label=\"Page navigation\">\n                    <ore-button\n                      class=\"dg-page-btn\"\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      icon-only\n                      label=\"Previous page\"\n                      disabled=\"${() => !effectiveHasPrev.value || isDisabled.value}\"\n                      @click=\"${() => handlePage('prev')}\">\n                      <ore-icon name=\"chevron-left\" size=\"14\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n                    </ore-button>\n                    <span\n                      class=\"dg-page-label\"\n                      dir=\"ltr\"\n                      aria-current=\"page\"\n                      role=\"status\"\n                      aria-live=\"polite\"\n                      aria-atomic=\"true\">\n                      ${() => effectivePageLabel.value}\n                    </span>\n                    <ore-button\n                      class=\"dg-page-btn\"\n                      variant=\"ghost\"\n                      size=\"sm\"\n                      icon-only\n                      label=\"Next page\"\n                      disabled=\"${() => !effectiveHasNext.value || isDisabled.value}\"\n                      @click=\"${() => handlePage('next')}\">\n                      <ore-icon name=\"chevron-right\" size=\"14\" stroke-width=\"2\" aria-hidden=\"true\"></ore-icon>\n                    </ore-button>\n                  </div>\n                </div>\n              </div>\n            `\n          : html``}\n    `;\n  },\n\n  shadow: { delegatesFocus: true },\n  styles: [tableBaseMixin('datagrid'), componentStyles],\n});\n"],"mappings":"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"}