@ixfx/components 0.7.2 → 0.7.3

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.
Files changed (74) hide show
  1. package/bundle/index.d.ts +198 -1
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +123 -8
  4. package/bundle/index.js.map +1 -1
  5. package/dist/ac-text.d.ts +1 -1
  6. package/dist/{ac-token--_vCHXWw.d.ts → ac-token-TCLAZeVg.d.ts} +2 -2
  7. package/dist/{ac-token--_vCHXWw.d.ts.map → ac-token-TCLAZeVg.d.ts.map} +1 -1
  8. package/dist/ac-token.d.ts +1 -1
  9. package/dist/button.d.ts +1 -1
  10. package/dist/button.d.ts.map +1 -1
  11. package/dist/colour-picker.d.ts +1 -1
  12. package/dist/crumbs.d.ts +1 -1
  13. package/dist/{data-grid-Bh4YrQGK.d.ts → data-grid-Db4QpnII.d.ts} +2 -2
  14. package/dist/{data-grid-Bh4YrQGK.d.ts.map → data-grid-Db4QpnII.d.ts.map} +1 -1
  15. package/dist/data-grid.d.ts +1 -1
  16. package/dist/data-grid.js +1 -1
  17. package/dist/{grouped-item-lister-DnWB-eFT.d.ts → grouped-item-lister-BWlC9S4e.d.ts} +2 -2
  18. package/dist/{grouped-item-lister-DnWB-eFT.d.ts.map → grouped-item-lister-BWlC9S4e.d.ts.map} +1 -1
  19. package/dist/grouped-item-lister.d.ts +1 -1
  20. package/dist/{icon-CDGprSdE.d.ts → icon-DR_3rSyZ.d.ts} +2 -2
  21. package/dist/icon-DR_3rSyZ.d.ts.map +1 -0
  22. package/dist/icons.d.ts +2 -2
  23. package/dist/{index-tq6f8007.d.ts → index-BOUaDIW-.d.ts} +2 -2
  24. package/dist/{index-tq6f8007.d.ts.map → index-BOUaDIW-.d.ts.map} +1 -1
  25. package/dist/{index-CoqtMSd9.d.ts → index-BUB5SICW.d.ts} +2 -2
  26. package/dist/{index-CoqtMSd9.d.ts.map → index-BUB5SICW.d.ts.map} +1 -1
  27. package/dist/{index-9AhaHIT2.d.ts → index-C59-JEPl.d.ts} +3 -3
  28. package/dist/{index-9AhaHIT2.d.ts.map → index-C59-JEPl.d.ts.map} +1 -1
  29. package/dist/{index-UvnoZUAo.d.ts → index-CJ5fshD3.d.ts} +2 -2
  30. package/dist/{index-UvnoZUAo.d.ts.map → index-CJ5fshD3.d.ts.map} +1 -1
  31. package/dist/{index-CS9yM8L-.d.ts → index-DsjpDksO.d.ts} +2 -2
  32. package/dist/{index-CS9yM8L-.d.ts.map → index-DsjpDksO.d.ts.map} +1 -1
  33. package/dist/index.d.ts +212 -14
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +123 -6
  36. package/dist/index.js.map +1 -1
  37. package/dist/{labelled-input-base-DiTme1JO.d.ts → labelled-input-base-Dp_9KP0G.d.ts} +2 -2
  38. package/dist/{labelled-input-base-DiTme1JO.d.ts.map → labelled-input-base-Dp_9KP0G.d.ts.map} +1 -1
  39. package/dist/labelled-radial-input.d.ts +2 -2
  40. package/dist/labelled-radial-input.d.ts.map +1 -1
  41. package/dist/labelled-range-input.d.ts +1 -1
  42. package/dist/labelled-slider-input.d.ts +1 -1
  43. package/dist/{menu-CXDFJxNm.js → menu-DMzjkVnK.js} +5 -5
  44. package/dist/menu-DMzjkVnK.js.map +1 -0
  45. package/dist/{menu-item-CZi9vy5O.d.ts → menu-item-ZmkXNjek.d.ts} +2 -2
  46. package/dist/{menu-item-CZi9vy5O.d.ts.map → menu-item-ZmkXNjek.d.ts.map} +1 -1
  47. package/dist/menu.d.ts +2 -2
  48. package/dist/menu.js +1 -1
  49. package/dist/miller.d.ts +1 -1
  50. package/dist/panel.d.ts +1 -1
  51. package/dist/{split-layout-JK3xWVX2.d.ts → split-layout-C5SYbDrq.d.ts} +2 -2
  52. package/dist/{split-layout-JK3xWVX2.d.ts.map → split-layout-C5SYbDrq.d.ts.map} +1 -1
  53. package/dist/split-layout.d.ts +1 -1
  54. package/dist/{tab-list-CGyPt07Z.d.ts → tab-list-DbMIL520.d.ts} +2 -2
  55. package/dist/{tab-list-CGyPt07Z.d.ts.map → tab-list-DbMIL520.d.ts.map} +1 -1
  56. package/dist/tabs.d.ts +1 -1
  57. package/dist/{tickled-controller-CRzdIEjJ.d.ts → tickled-controller-Ds1DMyG7.d.ts} +2 -2
  58. package/dist/{tickled-controller-CRzdIEjJ.d.ts.map → tickled-controller-Ds1DMyG7.d.ts.map} +1 -1
  59. package/dist/{titlebar-Bj5pfdw_.d.ts → titlebar-B23MrIbI.d.ts} +2 -2
  60. package/dist/{titlebar-Bj5pfdw_.d.ts.map → titlebar-B23MrIbI.d.ts.map} +1 -1
  61. package/dist/titlebar.d.ts +1 -1
  62. package/dist/tree.d.ts +1 -1
  63. package/dist/{vertical-list-ClnqTkF3.js → vertical-list-B4w_ksJk.js} +2 -2
  64. package/dist/{vertical-list-ClnqTkF3.js.map → vertical-list-B4w_ksJk.js.map} +1 -1
  65. package/dist/vertical-list.js +1 -1
  66. package/docs-user/README.md +1 -0
  67. package/docs-user/detail-list.md +42 -15
  68. package/docs-user/index.json +6 -2
  69. package/docs-user/infra-tickled.md +163 -0
  70. package/docs-user/llms.txt +1 -0
  71. package/llms.txt +1 -0
  72. package/package.json +1 -1
  73. package/dist/icon-CDGprSdE.d.ts.map +0 -1
  74. package/dist/menu-CXDFJxNm.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"vertical-list-ClnqTkF3.js","names":["#config","#selected","#anchor","#apply","#search","#customWrappers","#wrappedElements","customElement"],"sources":["../src/interaction/list-selection-controller.ts","../src/vertical-list/vertical-list.ts"],"sourcesContent":["import type { ListInteractionMode, ListSelectionMode } from './list-selection-types.js';\n\n/** Modifier state needed to route a click gesture per interaction mode. */\nexport type ListSelectionGesture = {\n readonly ctrlKey: boolean;\n readonly metaKey: boolean;\n readonly shiftKey: boolean;\n};\n\nexport type ListSelectionModes = {\n readonly selectionMode: ListSelectionMode;\n readonly interactionMode: ListInteractionMode;\n};\n\n/**\n * Configuration supplied by the host component.\n *\n * The controller owns selection *state* only — everything DOM- or\n * component-specific is expressed through these callbacks.\n */\nexport type ListSelectionControllerConfig<T> = {\n /** Visible items in navigation order — used for range selection and select-all. */\n readonly getOrderedItems: () => readonly T[];\n /** Whether an item may join the selection (e.g. reject `not-checked` rows). */\n readonly isSelectable: (item: T) => boolean;\n /** Current modes; read on every gesture so property changes apply immediately. */\n readonly getModes: () => ListSelectionModes;\n /**\n * Fired after every selection change. The host syncs DOM state\n * (`data-selected` / `data-checked`), dispatches `list-select`, and\n * requests an update.\n */\n readonly onChange: (selected: ReadonlySet<T>, previous: ReadonlySet<T>) => void;\n};\n\n/** Whether the interaction mode allows building a multi-item selection. */\nexport function isMultiMode(mode: ListInteractionMode): boolean {\n return mode === `checked` || mode === `sticky` || mode === `vscode` || mode === `standard`;\n}\n\n/**\n * Generic selection state machine shared by list components.\n *\n * Owns the selected set plus the range-selection anchor, the selection\n * primitives (`replace` / `toggle` / `rangeTo` / `clear` / `selectAll` /\n * `selectMany` / `deselect`) and the per-interactionMode gesture routers\n * (`handleClick` / `handleSpace`).\n *\n * Identity is generic: both `ixfx-vertical-list` and `ixfx-detail-list` use\n * `ListSelectionController<Element>`.\n *\n * @example\n * ```ts\n * const selection = new ListSelectionController<Element>({\n * getOrderedItems: () => this.getVisibleItems(),\n * isSelectable: el => !el.hasAttribute(`not-checked`),\n * getModes: () => ({ selectionMode: this.selectionMode, interactionMode: this.interactionMode }),\n * onChange: (selected, previous) => {\n * this.syncItemStates();\n * this.dispatchEvent(new CustomEvent(`list-select`, { detail: { selected, previous } }));\n * },\n * });\n * ```\n */\nexport class ListSelectionController<T> {\n readonly #config: ListSelectionControllerConfig<T>;\n #selected: ReadonlySet<T> = new Set();\n #anchor: T | undefined;\n\n constructor(config: ListSelectionControllerConfig<T>) {\n this.#config = config;\n }\n\n /** The current selection set (read-only). */\n get selected(): ReadonlySet<T> {\n return this.#selected;\n }\n\n /** Anchor for range (shift) selection — the last plainly-selected item. */\n get anchor(): T | undefined {\n return this.#anchor;\n }\n\n /** Whether \"select all\" (Cmd/Ctrl+A) is currently permitted. */\n canSelectAll(): boolean {\n return this.canSelectMultiple() && this.#config.getModes().selectionMode !== `none`;\n }\n\n /** Whether a multi-item selection can be built in the current modes. */\n canSelectMultiple(): boolean {\n const { selectionMode, interactionMode } = this.#config.getModes();\n return selectionMode === `multiple` || isMultiMode(interactionMode);\n }\n\n // --- Selection primitives ---\n\n /** Replace the entire selection with a single item; updates the anchor. */\n replace(item: T): void {\n if (this.#config.getModes().selectionMode === `none`)\n return;\n if (!this.#config.isSelectable(item))\n return;\n this.#anchor = item;\n this.#apply(new Set([item]));\n }\n\n /** Toggle a single item in/out of the selection without replacing it. Anchor unchanged. */\n toggle(item: T): void {\n const { selectionMode, interactionMode } = this.#config.getModes();\n if (!this.#config.isSelectable(item))\n return;\n // checked/vscode/sticky modes imply multiple selection capacity\n if (selectionMode === `none` && interactionMode !== `checked` && interactionMode !== `sticky`)\n return;\n\n const next = new Set(this.#selected);\n if (next.has(item)) {\n next.delete(item);\n } else {\n if (!this.canSelectMultiple())\n next.clear();\n next.add(item);\n }\n this.#apply(next);\n }\n\n /** Select the contiguous ordered range from the anchor to `item`. Anchor unchanged. */\n rangeTo(item: T): void {\n if (this.#config.getModes().selectionMode === `none`)\n return;\n if (this.#anchor === undefined) {\n this.replace(item);\n return;\n }\n const ordered = this.#config.getOrderedItems();\n const anchorIdx = ordered.indexOf(this.#anchor);\n const targetIdx = ordered.indexOf(item);\n if (anchorIdx === -1 || targetIdx === -1) {\n this.replace(item);\n return;\n }\n const start = Math.min(anchorIdx, targetIdx);\n const end = Math.max(anchorIdx, targetIdx);\n this.#apply(new Set(ordered.slice(start, end + 1).filter(t => this.#config.isSelectable(t))));\n }\n\n /** Clear the selection. No change event when the selection is already empty. */\n clear(): void {\n if (this.#selected.size === 0)\n return;\n this.#apply(new Set());\n }\n\n /** Select every ordered item that passes `isSelectable`. */\n selectAll(): void {\n const ordered = this.#config.getOrderedItems();\n if (ordered.length === 0)\n return;\n const next = new Set(ordered.filter(t => this.#config.isSelectable(t)));\n if (next.size === 0)\n return;\n this.#apply(next);\n }\n\n /**\n * Replace the selection with many items at once. Requires\n * `selectionMode=\"multiple\"`, throws otherwise. Updates the anchor.\n */\n selectMany(items: Iterable<T>): void {\n const { selectionMode } = this.#config.getModes();\n if (selectionMode !== `multiple`) {\n throw new Error(`selectMany requires selectionMode=\"multiple\" (current: \"${selectionMode}\")`);\n }\n const next = new Set([...items].filter(t => this.#config.isSelectable(t)));\n if (next.size === 0)\n return;\n this.#anchor = [...next].at(-1);\n this.#apply(next);\n }\n\n /** Remove a single item from the selection. */\n deselect(item: T): void {\n if (!this.#selected.has(item))\n return;\n const next = new Set(this.#selected);\n next.delete(item);\n this.#apply(next);\n }\n\n // --- Gesture routers ---\n\n /** Route a click on `item` according to the current interaction mode. */\n handleClick(item: T, gesture: ListSelectionGesture): void {\n const { selectionMode, interactionMode: mode } = this.#config.getModes();\n if (mode === `manual`)\n return;\n if (selectionMode === `none` && mode !== `checked` && mode !== `sticky`)\n return;\n\n switch (mode) {\n case `implicit`:\n // Single-select only; modifier keys have no effect\n this.replace(item);\n break;\n\n case `standard`:\n case `vscode`:\n if (gesture.ctrlKey || gesture.metaKey) {\n // Ctrl/Cmd+click: toggle item; requires effective multi-select\n if (selectionMode === `multiple` || mode === `vscode`) {\n this.toggle(item);\n }\n } else if (gesture.shiftKey && (selectionMode === `multiple` || mode === `vscode`)) {\n // Shift+click: range-select from anchor\n this.rangeTo(item);\n } else {\n // Plain click: replace selection, update anchor\n this.replace(item);\n }\n break;\n\n case `checked`:\n // Body clicks do nothing — only the checkbox column toggles.\n break;\n\n case `sticky`:\n // Every tap/click toggles — multi-select without modifier keys (touchscreens).\n this.toggle(item);\n break;\n }\n }\n\n /** Route a Space keypress on `item` according to the current interaction mode. */\n handleSpace(item: T): void {\n const mode = this.#config.getModes().interactionMode;\n if (mode === `manual`)\n return;\n if (mode === `implicit`) {\n // Space selects (same as click) in implicit mode\n this.replace(item);\n } else {\n // standard / vscode / checked / sticky: Space toggles\n this.toggle(item);\n }\n }\n\n // --- Maintenance ---\n\n /** Drop the range-selection anchor (e.g. when the interaction mode changes). */\n clearAnchor(): void {\n this.#anchor = undefined;\n }\n\n /**\n * Re-key the selection across an identity boundary — e.g. when items are\n * wrapped/unwrapped and the selected Element references change. Silent:\n * no `onChange` is fired.\n */\n remap(fn: (item: T) => T): void {\n if (this.#selected.size === 0)\n return;\n this.#selected = new Set([...this.#selected].map(fn));\n }\n\n /**\n * Hard reset: empty the selection and clear the anchor without firing\n * `onChange` (e.g. when all items are removed).\n */\n reset(): void {\n this.#selected = new Set();\n this.#anchor = undefined;\n }\n\n /**\n * Replace the selection and anchor silently (no `onChange`). Used to\n * re-key the selection when items are rebuilt from data — e.g. matching\n * new elements to previously selected keys.\n */\n seed(items: Iterable<T>, anchor?: T): void {\n this.#selected = new Set(items);\n this.#anchor = anchor;\n }\n\n #apply(next: ReadonlySet<T>): void {\n const previous = this.#selected;\n this.#selected = next;\n this.#config.onChange(next, previous);\n }\n}\n","import type { CommandId } from '@clinth/ui-commands';\nimport type { PropertyValues } from 'lit';\nimport type { ElementIncrSearch } from '../incr-search/element-search.js';\nimport type {\n ListActivateDetail,\n ListElementEventMap,\n ListInteractionMode,\n ListItemClickDetail,\n ListSelectDetail,\n ListSelectionMode,\n ListTickleDetail,\n} from '../interaction/list-selection-types.js';\nimport { css, html, LitElement, render } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\nimport { createElementIncrSearch } from '../incr-search/element-search.js';\nimport { ListSelectionController } from '../interaction/list-selection-controller.js';\nimport { TickledController } from '../interaction/tickled-controller.js';\nimport { themeFallbacks } from '../styles/fallbacks.js';\nimport { safeCustomElement as customElement } from '../util/custom-elements.js';\n\nexport type {\n ListActivateDetail,\n ListActivateEvent,\n ListElementEventMap,\n ListInteractionMode,\n ListItemClickDetail,\n ListItemClickEvent,\n ListSelectDetail,\n ListSelectEvent,\n ListSelectionMode,\n ListTickleDetail,\n ListTickleEvent,\n} from './types.js';\n\nexport interface CustomCheckboxOptions {\n renderer: (checked: boolean) => unknown;\n margin?: string;\n width?: string;\n}\n\nexport interface AddItemOptions {\n /**\n * In `checked` interaction mode, adds the item without a checkbox column and\n * without the ability to be checked/selected. Equivalent to setting the\n * `not-checked` attribute on the item. Ignored outside `checked` mode.\n */\n notChecked?: boolean;\n}\n\nconst ITEM_SELECTOR = `li, [data-list-item]`;\n\n@customElement(`ixfx-vertical-list`)\nexport class VerticalListElement extends LitElement {\n @property({ type: String, attribute: `selection-mode` })\n selectionMode: ListSelectionMode = `single`;\n\n @property({ type: String, attribute: `interaction-mode` })\n interactionMode: ListInteractionMode = `standard`;\n\n @property({ attribute: false })\n filterPredicate: ((el: Element) => boolean) | undefined;\n\n /**\n * Whether keyboard navigation wraps from last to first and vice versa.\n * - 'stop' (default): navigation stops at the boundary\n * - 'wrap': navigation wraps around\n */\n @property({ type: String, attribute: `item-wrap` })\n itemWrap: `stop` | `wrap` = `stop`;\n\n /**\n * Optional renderer for the checkbox shown in `checked` interactionMode.\n * Receives the current `checked` state and returns a Lit template result.\n *\n * When set, the per-row checkbox becomes a `<span role=\"checkbox\">` whose\n * contents are whatever this returns (rendered with `lit`'s `render()`).\n * When unset, a native `<input type=\"checkbox\">` is used.\n *\n * @example\n * list.checkboxRenderer = (checked) => checked\n * ? html`<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\"\n * fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n * stroke-linecap=\"round\" stroke-linejoin=\"round\"\n * style=\"opacity:0.5\">\n * <polyline points=\"3,8 7,12 13,4\" />\n * </svg>`\n * : html``;\n */\n @property({ attribute: false })\n checkboxRenderer: ((checked: boolean) => unknown) | CustomCheckboxOptions | undefined;\n\n @state() private _searchVisible = false;\n @state() private _searchQuery = ``;\n\n @query(`.search-input`)\n private _searchInputEl!: HTMLInputElement;\n\n #search: ElementIncrSearch | undefined;\n #customWrappers = new Map<HTMLLIElement, Element>();\n #wrappedElements = new Set<Element>();\n\n private readonly selection = new ListSelectionController<Element>({\n getOrderedItems: () => this._getVisibleItems(),\n isSelectable: el => this._isCheckable(el),\n getModes: () => ({ selectionMode: this.selectionMode, interactionMode: this.interactionMode }),\n onChange: (selected, previous) => {\n this._syncItemStates();\n this._dispatch<ListSelectDetail>(`list-select`, { selected, previous });\n },\n });\n\n private tickled = new TickledController<Element>(this, {\n getItems: () => this._getVisibleItems().map(el => ({\n id: el,\n enabled: !el.hasAttribute(`not-checked`),\n })),\n onTickle: (id) => {\n if (id !== undefined) {\n this._dispatch<ListTickleDetail>(`list-tickle`, { item: id });\n }\n },\n onStateChange: () => this._syncItemStates(),\n itemWrap: this.itemWrap,\n });\n\n override connectedCallback(): void {\n super.connectedCallback();\n if (!this.hasAttribute(`tabindex`))\n this.tabIndex = 0;\n this.addEventListener(`pointerover`, this._onPointerOver);\n this.addEventListener(`pointerleave`, this._onPointerLeave);\n this.addEventListener(`keydown`, this._onKeyDown);\n this.addEventListener(`click`, this._onClick);\n this.addEventListener(`dblclick`, this._onDblClick);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener(`pointerover`, this._onPointerOver);\n this.removeEventListener(`pointerleave`, this._onPointerLeave);\n this.removeEventListener(`keydown`, this._onKeyDown);\n this.removeEventListener(`click`, this._onClick);\n this.removeEventListener(`dblclick`, this._onDblClick);\n this.#search?.dispose();\n this.#search = undefined;\n }\n\n override firstUpdated(): void {\n this.#search = createElementIncrSearch({\n container: this,\n itemSelector: ITEM_SELECTOR,\n getLabel: el => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,\n getKey: el =>\n el.getAttribute(`data-key`)\n ?? el.getAttribute(`data-search-label`)\n ?? el.textContent?.trim()\n ?? ``,\n highlightKey: `vertical-list-search`,\n });\n }\n\n override updated(changed: PropertyValues): void {\n super.updated(changed);\n if (changed.has(`interactionMode`)) {\n this.selection.clearAnchor();\n const previousSelected = this.selection.selected;\n // Save wrapper→inner map before _syncCheckboxes() clears it on unwrap\n const leavingChecked = (changed.get(`interactionMode`) as ListInteractionMode) === `checked`;\n const savedWrappers = leavingChecked ? new Map(this.#customWrappers) : null;\n\n this._syncCheckboxes();\n\n // Remap the selection across the wrap/unwrap element-reference boundary\n if (previousSelected.size > 0) {\n if (savedWrappers && savedWrappers.size > 0) {\n // Leaving checked: wrapper refs → inner element refs\n this.selection.remap(el => savedWrappers.get(el as HTMLLIElement) ?? el);\n } else if (this.#customWrappers.size > 0) {\n // Entering checked: inner element refs → wrapper refs\n const innerToWrapper = new Map([...this.#customWrappers].map(([w, i]) => [i, w]));\n this.selection.remap(el => innerToWrapper.get(el) ?? el);\n }\n }\n\n this._syncItemStates();\n } else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) {\n this._syncCheckboxes();\n }\n }\n\n // --- Public API ---\n\n /** The most recently selected item, or `undefined` if nothing is selected. */\n get selectedItem(): Element | undefined {\n return [...this.selection.selected].at(-1);\n }\n\n /** The full current selection set (read-only). */\n get selectedItems(): ReadonlySet<Element> {\n return this.selection.selected;\n }\n\n addItem(item: Element | string, options?: AddItemOptions): void {\n let el: Element;\n if (typeof item === `string`) {\n const li = document.createElement(`li`);\n li.textContent = item;\n el = li;\n } else {\n el = item;\n }\n if (options?.notChecked)\n el.setAttribute(`not-checked`, ``);\n this.appendChild(el);\n if (this.interactionMode === `checked`)\n this._syncCheckboxes();\n }\n\n removeItem(item: Element): void {\n item.remove();\n }\n\n clearItems(): void {\n this.#customWrappers.clear();\n this.replaceChildren();\n this.selection.reset();\n this._syncItemStates();\n }\n\n /**\n * Replace the entire item list in one call.\n * Equivalent to `clearItems()` followed by `addItem()` for each element.\n * Preserves `interactionMode` checkbox wrapping.\n */\n setItems(items: readonly Element[]): void {\n this.clearItems();\n for (const item of items) {\n this.appendChild(item);\n }\n if (this.interactionMode === `checked`)\n this._syncCheckboxes();\n }\n\n /**\n * Dispatch a command to this list. No-op by default.\n * Intended for use with `ixfx-grouped-item-lister`'s `dispatch()` method,\n * which forwards commands to all sub-components.\n */\n dispatch(_commandId: CommandId, _args?: Record<string, unknown>): void {}\n\n select(item: Element): void {\n this.selection.replace(item);\n }\n\n selectMany(items: Iterable<Element>): void {\n this.selection.selectMany(items);\n }\n\n deselect(item: Element): void {\n this.selection.deselect(item);\n }\n\n clearSelection(): void {\n this.selection.clear();\n }\n\n selectAll(): void {\n if (this.selection.canSelectAll())\n this.selection.selectAll();\n }\n\n * selectedFilter(kind: `selected` | `checked` | `either`): Generator<Element> {\n const wanted = (el: Element) => {\n switch (kind) {\n case `selected`: return el.hasAttribute(`data-selected`);\n case `checked`: return el.hasAttribute(`data-checked`);\n case `either`: return el.hasAttribute(`data-selected`) || el.hasAttribute(`data-checked`);\n }\n };\n\n for (const el of this.querySelectorAll(ITEM_SELECTOR)) {\n if (this.#wrappedElements.has(el))\n continue;\n if ((el as HTMLElement).hidden)\n continue;\n if (wanted(el))\n yield el;\n }\n }\n\n // --- Helpers ---\n\n private _getVisibleItems(): Element[] {\n return Array.from(this.querySelectorAll(ITEM_SELECTOR)).filter((el) => {\n if (this.#wrappedElements.has(el))\n return false;\n if ((el as HTMLElement).hidden)\n return false;\n if (this.filterPredicate && !this.filterPredicate(el))\n return false;\n return true;\n });\n }\n\n // Whether an item participates in checked-mode selection. Items carrying the\n // `not-checked` attribute are skipped; outside `checked` mode it has no effect.\n // For wrapped custom elements the flag lives on the inner element, so resolve\n // through the wrapper map.\n private _isCheckable(item: Element): boolean {\n if (this.interactionMode !== `checked`)\n return true;\n const real = this.#customWrappers.get(item as HTMLLIElement) ?? item;\n return !real.hasAttribute(`not-checked`);\n }\n\n private _syncItemStates(): void {\n for (const el of this.querySelectorAll(ITEM_SELECTOR)) {\n if (this.#wrappedElements.has(el))\n continue;\n const item = this.#customWrappers.get(el as HTMLLIElement) ?? el;\n el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));\n el.toggleAttribute(`data-selected`, this.selection.selected.has(el));\n if (this.interactionMode === `checked`) {\n el.toggleAttribute(`data-checked`, !item.hasAttribute(`not-checked`) && this.selection.selected.has(el));\n } else {\n el.removeAttribute(`data-checked`);\n }\n }\n this.dataset.inputMode = this.tickled.inputMode;\n this._syncCheckboxes();\n }\n\n private _syncCheckboxes(): void {\n const inCheckedMode = this.interactionMode === `checked`;\n const wantsCustom = typeof this.checkboxRenderer === `function`;\n\n if (inCheckedMode) {\n for (const inner of Array.from(this.children)) {\n if (!inner.hasAttribute(`data-list-item`))\n continue;\n if (inner.parentElement !== this)\n continue; // idempotency: already wrapped\n const wrapper = document.createElement(`li`);\n wrapper.className = `ixfx-custom-row`;\n const key = inner.getAttribute(`data-key`);\n const label = inner.getAttribute(`data-search-label`);\n if (key !== null)\n wrapper.setAttribute(`data-key`, key);\n if (label !== null)\n wrapper.setAttribute(`data-search-label`, label);\n inner.removeAttribute(`data-list-item`);\n this.insertBefore(wrapper, inner);\n wrapper.appendChild(inner);\n this.#customWrappers.set(wrapper, inner);\n this.#wrappedElements.add(inner);\n }\n } else {\n for (const [wrapper, inner] of this.#customWrappers) {\n if (wrapper.parentElement !== this)\n continue;\n inner.setAttribute(`data-list-item`, ``);\n this.insertBefore(inner, wrapper);\n wrapper.remove();\n }\n this.#customWrappers.clear();\n this.#wrappedElements.clear();\n }\n\n for (const el of this.querySelectorAll<HTMLElement>(`li`)) {\n const existing = el.querySelector<HTMLElement>(`.ixfx-list-checkbox`);\n if (!inCheckedMode) {\n if (existing)\n existing.remove();\n continue;\n }\n // Rows marked `not-checked` get no checkbox column and can't be checked\n const itemEl = this.#customWrappers.get(el as HTMLLIElement) ?? el;\n if (itemEl.hasAttribute(`not-checked`)) {\n if (existing)\n existing.remove();\n continue;\n }\n\n // In checked mode: if the existing checkbox is the wrong variant, drop it.\n const existingIsCustom = existing?.classList.contains(`ixfx-list-checkbox-custom`) ?? false;\n if (existing && existingIsCustom !== wantsCustom)\n existing.remove();\n\n let cb: HTMLElement | null = el.querySelector<HTMLElement>(`.ixfx-list-checkbox`);\n if (!cb) {\n if (wantsCustom) {\n const opts = this.checkboxRenderer as CustomCheckboxOptions;\n const margin = opts.margin ?? `0`;\n const width = opts.width ?? `24px`;\n cb = this._createCustomCheckbox(el, margin, width);\n } else {\n cb = this._createNativeCheckboxCell(el);\n }\n el.prepend(cb);\n }\n\n const checked = this.selection.selected.has(el);\n if (wantsCustom) {\n cb.setAttribute(`aria-checked`, String(checked));\n cb.toggleAttribute(`data-checked`, checked);\n if (this.checkboxRenderer) {\n const renderer = typeof this.checkboxRenderer === `function`\n ? this.checkboxRenderer\n : (this.checkboxRenderer as CustomCheckboxOptions).renderer;\n render(renderer(checked), cb);\n }\n } else {\n const input = cb.querySelector<HTMLInputElement>(`input.ixfx-list-checkbox`);\n if (input)\n input.checked = checked;\n }\n }\n }\n\n /**\n * Native checkbox cell: a span that stretches to the row's full cross-axis\n * (so the entire column height is a click target) with a real `<input>`\n * centred inside. The span owns the click; the input is purely visual and\n * has `pointer-events:none` so a single click produces one toggle.\n */\n private _createNativeCheckboxCell(el: HTMLElement): HTMLSpanElement {\n const cell = document.createElement(`span`);\n cell.className = `ixfx-list-checkbox ixfx-list-checkbox-native`;\n cell.style.cssText = `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;align-self:stretch;width:18px;margin:0 6px 0 0;cursor:pointer;pointer-events:auto`;\n cell.tabIndex = -1;\n cell.setAttribute(`role`, `checkbox`);\n cell.setAttribute(`part`, `checkbox`);\n cell.addEventListener(`click`, (e) => {\n e.stopPropagation();\n this.selection.toggle(el);\n });\n const input = document.createElement(`input`);\n input.type = `checkbox`;\n input.className = `ixfx-list-checkbox`;\n input.style.cssText = `pointer-events:none;cursor:pointer;margin:0`;\n cell.appendChild(input);\n return cell;\n }\n\n /**\n * Custom checkbox cell: same shape as the native cell (a stretching span)\n * so behaviour is identical. The renderer's content goes inside.\n */\n private _createCustomCheckbox(el: HTMLElement, margin: string, width: string): HTMLSpanElement {\n const span = document.createElement(`span`);\n span.className = `ixfx-list-checkbox ixfx-list-checkbox-custom`;\n span.style.cssText = `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;align-self:stretch;width:${width};margin:${margin};cursor:pointer;pointer-events:auto`;\n span.tabIndex = -1;\n span.setAttribute(`role`, `checkbox`);\n span.setAttribute(`part`, `checkbox`);\n span.addEventListener(`click`, (e) => {\n e.stopPropagation();\n this.selection.toggle(el);\n });\n return span;\n }\n\n private _findListItem(path: EventTarget[]): Element | undefined {\n for (const target of path) {\n if (!(target instanceof Element))\n continue;\n if (target === this)\n break;\n if (target.parentElement === this && target.matches(ITEM_SELECTOR))\n return target;\n }\n return undefined;\n }\n\n private _dispatch<T>(name: keyof ListElementEventMap, detail: T): void {\n this.dispatchEvent(new CustomEvent<T>(name as string, {\n detail,\n bubbles: true,\n composed: true,\n }));\n }\n\n private _openSearch(): void {\n this._searchVisible = true;\n void this.updateComplete.then(() => this._searchInputEl?.focus());\n }\n\n private _closeSearch(): void {\n this._searchVisible = false;\n this._searchQuery = ``;\n void this.#search?.clearQuery();\n this.focus();\n }\n\n // --- Event handlers ---\n\n private _onPointerOver = (e: PointerEvent): void => {\n const item = this._findListItem(e.composedPath());\n if (item)\n this.tickled.handlePointerEnter(item);\n };\n\n private _onPointerLeave = (e: PointerEvent): void => {\n this.tickled.handlePointerLeave(e);\n };\n\n private _onKeyDown = (e: KeyboardEvent): void => {\n if ((e.ctrlKey || e.metaKey) && e.key === `f`) {\n e.preventDefault();\n this._openSearch();\n return;\n }\n if ((e.ctrlKey || e.metaKey) && e.key === `a`) {\n if (this.selection.canSelectAll()) {\n e.preventDefault();\n this.selection.selectAll();\n }\n return;\n }\n if (this._searchVisible)\n return;\n\n switch (e.key) {\n case `ArrowDown`: {\n e.preventDefault();\n this.tickled.handleKeyboardNavigation(`next`);\n // vscode: Shift+Arrow extends range from anchor\n if (e.shiftKey && this.interactionMode === `vscode`) {\n const cur = this.tickled.tickledId;\n if (cur)\n this.selection.rangeTo(cur);\n }\n break;\n }\n case `ArrowUp`: {\n e.preventDefault();\n this.tickled.handleKeyboardNavigation(`previous`);\n if (e.shiftKey && this.interactionMode === `vscode`) {\n const cur = this.tickled.tickledId;\n if (cur)\n this.selection.rangeTo(cur);\n }\n break;\n }\n case `Enter`: {\n const id = this.tickled.tickledId;\n if (id) {\n e.preventDefault();\n this._dispatch<ListItemClickDetail>(`list-item-click`, { item: id });\n this._activateItem(id);\n }\n break;\n }\n case ` `: {\n const id = this.tickled.tickledId;\n if (id) {\n e.preventDefault();\n this.selection.handleSpace(id);\n }\n break;\n }\n case `Escape`:\n this.selection.clear();\n break;\n }\n };\n\n private _onClick = (e: MouseEvent): void => {\n const item = this._findListItem(e.composedPath());\n if (!item)\n return;\n this._dispatch<ListItemClickDetail>(`list-item-click`, { item });\n this.selection.handleClick(item, e);\n };\n\n private _onDblClick = (e: MouseEvent): void => {\n const item = this._findListItem(e.composedPath());\n if (item)\n this._activateItem(item);\n };\n\n private _activateItem(item: Element): void {\n // manual mode: never auto-select; just fire the event\n if (this.interactionMode !== `manual`) {\n this.selection.replace(item);\n }\n this._dispatch<ListActivateDetail>(`list-activate`, { item });\n }\n\n private _onSearchInput = (e: Event): void => {\n const input = e.target as HTMLInputElement;\n this._searchQuery = input.value;\n void this.#search?.setQuery(this._searchQuery);\n };\n\n private _onSearchKeydown = (e: KeyboardEvent): void => {\n if (e.key === `Escape`) {\n e.stopPropagation();\n this._closeSearch();\n } else if (e.key === `ArrowDown`) {\n e.preventDefault();\n this.tickled.handleKeyboardNavigation(`next`);\n } else if (e.key === `ArrowUp`) {\n e.preventDefault();\n this.tickled.handleKeyboardNavigation(`previous`);\n } else if (e.key === `Enter`) {\n e.preventDefault();\n e.stopPropagation();\n const id = this.tickled.tickledId ?? this._getVisibleItems()[0];\n if (id) {\n this._dispatch<ListItemClickDetail>(`list-item-click`, { item: id });\n this._activateItem(id);\n this._closeSearch();\n }\n }\n };\n\n // --- Styles ---\n\n static override styles = [themeFallbacks, css`\n :host {\n display: block;\n position: relative;\n background: var(--list-bg, var(--surface-3));\n color: var(--list-text, var(--surface-3-text));\n border-radius: var(--list-radius, var(--radius-s));\n border: 1px solid var(--list-border, var(--border));\n width: 100%;\n height: 100%;\n overflow: hidden;\n box-sizing: border-box;\n outline: none;\n --ixfx-list-item-padding: var(--ixfx-list-item-padding, var(--list-item-padding, 4px var(--space-m, 8px)));\n }\n\n :host(:focus-visible) {\n outline: 2px solid var(--accent);\n outline-offset: 2px;\n }\n\n .container {\n position: relative;\n width: 100%;\n height: 100%;\n }\n\n ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n height: 100%;\n overflow-x: hidden;\n overflow-y: auto;\n box-sizing: border-box;\n /* Scrollbar theming: end users override these CSS variables to control\n the native scrollbar (scrollbar-width, scrollbar-gutter, scrollbar-color). */\n scrollbar-width: var(--list-scrollbar-width, auto);\n scrollbar-gutter: var(--list-scrollbar-gutter, auto);\n scrollbar-color: var(--list-scrollbar-color, auto);\n }\n\n ::slotted([hidden]) {\n display: none !important;\n }\n\n ::slotted(li) {\n display: flex;\n align-items: center;\n padding: var(--ixfx-list-item-padding);\n min-height: var(--list-item-height, 28px);\n cursor: pointer;\n user-select: none;\n transition: background var(--transition, 0.1s ease-out),\n outline-color var(--transition, 0.1s ease-out);\n box-sizing: border-box;\n outline: 1px solid transparent;\n }\n\n /* Custom shadow-DOM components handle their own internal layout/padding */\n ::slotted([data-list-item]) {\n display: block;\n cursor: pointer;\n user-select: none;\n transition: background var(--transition, 0.1s ease-out),\n outline-color var(--transition, 0.1s ease-out);\n outline: 1px solid transparent;\n }\n\n /* In checked mode, only the checkbox column is clickable; the row body\n gets a default cursor so it doesn't suggest otherwise. */\n :host([interaction-mode=\"checked\"]) ::slotted(li),\n :host([interaction-mode=\"checked\"]) ::slotted([data-list-item]) {\n cursor: default;\n }\n\n /* Wrapper inserted around custom [data-list-item] elements in checked mode.\n Provides the flex layout so the injected checkbox can stretch to the\n row's full height via align-self:stretch. */\n .ixfx-custom-row {\n display: flex;\n align-items: center;\n min-height: var(--list-item-height, 28px);\n box-sizing: border-box;\n cursor: default;\n user-select: none;\n }\n\n ::slotted([data-tickled]) {\n background: var(--item-bg-tickled);\n outline: var(--item-border-tickled);\n }\n\n ::slotted([data-selected]) {\n background: var(--item-bg-selected);\n color: var(--item-text-selected);\n outline: 1px solid transparent;\n }\n\n ::slotted([data-selected][data-tickled]) {\n background: var(--item-bg-selected-tickled);\n outline: 2px dashed var(--surface-3);\n outline-offset: -2px;\n }\n\n /* checked mode: checkbox (injected into li) communicates selection — suppress bg highlight */\n :host([interaction-mode=\"checked\"]) ::slotted([data-selected]) {\n background: transparent;\n color: inherit;\n outline: 1px solid transparent;\n }\n\n :host([interaction-mode=\"checked\"]) ::slotted([data-selected][data-tickled]) {\n background: var(--item-bg-tickled);\n color: inherit;\n outline: var(--item-border-tickled);\n }\n\n .search-overlay {\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n display: flex;\n align-items: center;\n gap: 4px;\n background: var(--surface-4, #2a2a2a);\n border: 1px solid var(--border, #444);\n border-radius: var(--radius-m, 4px);\n padding: 4px 6px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n }\n\n .search-overlay[hidden] {\n display: none;\n }\n\n .search-input {\n background: transparent;\n border: none;\n outline: none;\n color: inherit;\n font-size: var(--text-m, 0.875em);\n min-width: 140px;\n }\n\n .search-close {\n background: transparent;\n border: none;\n color: var(--surface-muted-text, #888);\n cursor: pointer;\n font-size: 1.1em;\n line-height: 1;\n padding: 0 2px;\n }\n\n .search-close:hover {\n color: inherit;\n }\n `];\n\n // --- Render ---\n\n override render() {\n return html`\n <div class=\"container\">\n <ul part=\"list\">\n <slot></slot>\n </ul>\n <div\n class=\"search-overlay\"\n part=\"search-overlay\"\n ?hidden=${!this._searchVisible}\n >\n <input\n type=\"text\"\n class=\"search-input\"\n placeholder=\"Search…\"\n .value=${this._searchQuery}\n @input=${this._onSearchInput}\n @keydown=${this._onSearchKeydown}\n />\n <button class=\"search-close\" @click=${this._closeSearch} title=\"Close search (Esc)\">×</button>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ixfx-vertical-list': VerticalListElement;\n }\n\n interface HTMLElementEventMap extends ListElementEventMap {}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"vertical-list-B4w_ksJk.js","names":["#config","#selected","#anchor","#apply","#search","#customWrappers","#wrappedElements","customElement"],"sources":["../src/interaction/list-selection-controller.ts","../src/vertical-list/vertical-list.ts"],"sourcesContent":["import type { ListInteractionMode, ListSelectionMode } from './list-selection-types.js';\n\n/** Modifier state needed to route a click gesture per interaction mode. */\nexport type ListSelectionGesture = {\n readonly ctrlKey: boolean;\n readonly metaKey: boolean;\n readonly shiftKey: boolean;\n};\n\nexport type ListSelectionModes = {\n readonly selectionMode: ListSelectionMode;\n readonly interactionMode: ListInteractionMode;\n};\n\n/**\n * Configuration supplied by the host component.\n *\n * The controller owns selection *state* only — everything DOM- or\n * component-specific is expressed through these callbacks.\n */\nexport type ListSelectionControllerConfig<T> = {\n /** Visible items in navigation order — used for range selection and select-all. */\n readonly getOrderedItems: () => readonly T[];\n /** Whether an item may join the selection (e.g. reject `not-checked` rows). */\n readonly isSelectable: (item: T) => boolean;\n /** Current modes; read on every gesture so property changes apply immediately. */\n readonly getModes: () => ListSelectionModes;\n /**\n * Fired after every selection change. The host syncs DOM state\n * (`data-selected` / `data-checked`), dispatches `list-select`, and\n * requests an update.\n */\n readonly onChange: (selected: ReadonlySet<T>, previous: ReadonlySet<T>) => void;\n};\n\n/** Whether the interaction mode allows building a multi-item selection. */\nexport function isMultiMode(mode: ListInteractionMode): boolean {\n return mode === `checked` || mode === `sticky` || mode === `vscode` || mode === `standard`;\n}\n\n/**\n * Generic selection state machine shared by list components.\n *\n * Owns the selected set plus the range-selection anchor, the selection\n * primitives (`replace` / `toggle` / `rangeTo` / `clear` / `selectAll` /\n * `selectMany` / `deselect`) and the per-interactionMode gesture routers\n * (`handleClick` / `handleSpace`).\n *\n * Identity is generic: both `ixfx-vertical-list` and `ixfx-detail-list` use\n * `ListSelectionController<Element>`.\n *\n * @example\n * ```ts\n * const selection = new ListSelectionController<Element>({\n * getOrderedItems: () => this.getVisibleItems(),\n * isSelectable: el => !el.hasAttribute(`not-checked`),\n * getModes: () => ({ selectionMode: this.selectionMode, interactionMode: this.interactionMode }),\n * onChange: (selected, previous) => {\n * this.syncItemStates();\n * this.dispatchEvent(new CustomEvent(`list-select`, { detail: { selected, previous } }));\n * },\n * });\n * ```\n */\nexport class ListSelectionController<T> {\n readonly #config: ListSelectionControllerConfig<T>;\n #selected: ReadonlySet<T> = new Set();\n #anchor: T | undefined;\n\n constructor(config: ListSelectionControllerConfig<T>) {\n this.#config = config;\n }\n\n /** The current selection set (read-only). */\n get selected(): ReadonlySet<T> {\n return this.#selected;\n }\n\n /** Anchor for range (shift) selection — the last plainly-selected item. */\n get anchor(): T | undefined {\n return this.#anchor;\n }\n\n /** Whether \"select all\" (Cmd/Ctrl+A) is currently permitted. */\n canSelectAll(): boolean {\n return this.canSelectMultiple() && this.#config.getModes().selectionMode !== `none`;\n }\n\n /** Whether a multi-item selection can be built in the current modes. */\n canSelectMultiple(): boolean {\n const { selectionMode, interactionMode } = this.#config.getModes();\n return selectionMode === `multiple` || isMultiMode(interactionMode);\n }\n\n // --- Selection primitives ---\n\n /** Replace the entire selection with a single item; updates the anchor. */\n replace(item: T): void {\n if (this.#config.getModes().selectionMode === `none`)\n return;\n if (!this.#config.isSelectable(item))\n return;\n this.#anchor = item;\n this.#apply(new Set([item]));\n }\n\n /** Toggle a single item in/out of the selection without replacing it. Anchor unchanged. */\n toggle(item: T): void {\n const { selectionMode, interactionMode } = this.#config.getModes();\n if (!this.#config.isSelectable(item))\n return;\n // checked/vscode/sticky modes imply multiple selection capacity\n if (selectionMode === `none` && interactionMode !== `checked` && interactionMode !== `sticky`)\n return;\n\n const next = new Set(this.#selected);\n if (next.has(item)) {\n next.delete(item);\n } else {\n if (!this.canSelectMultiple())\n next.clear();\n next.add(item);\n }\n this.#apply(next);\n }\n\n /** Select the contiguous ordered range from the anchor to `item`. Anchor unchanged. */\n rangeTo(item: T): void {\n if (this.#config.getModes().selectionMode === `none`)\n return;\n if (this.#anchor === undefined) {\n this.replace(item);\n return;\n }\n const ordered = this.#config.getOrderedItems();\n const anchorIdx = ordered.indexOf(this.#anchor);\n const targetIdx = ordered.indexOf(item);\n if (anchorIdx === -1 || targetIdx === -1) {\n this.replace(item);\n return;\n }\n const start = Math.min(anchorIdx, targetIdx);\n const end = Math.max(anchorIdx, targetIdx);\n this.#apply(new Set(ordered.slice(start, end + 1).filter(t => this.#config.isSelectable(t))));\n }\n\n /** Clear the selection. No change event when the selection is already empty. */\n clear(): void {\n if (this.#selected.size === 0)\n return;\n this.#apply(new Set());\n }\n\n /** Select every ordered item that passes `isSelectable`. */\n selectAll(): void {\n const ordered = this.#config.getOrderedItems();\n if (ordered.length === 0)\n return;\n const next = new Set(ordered.filter(t => this.#config.isSelectable(t)));\n if (next.size === 0)\n return;\n this.#apply(next);\n }\n\n /**\n * Replace the selection with many items at once. Requires\n * `selectionMode=\"multiple\"`, throws otherwise. Updates the anchor.\n */\n selectMany(items: Iterable<T>): void {\n const { selectionMode } = this.#config.getModes();\n if (selectionMode !== `multiple`) {\n throw new Error(`selectMany requires selectionMode=\"multiple\" (current: \"${selectionMode}\")`);\n }\n const next = new Set([...items].filter(t => this.#config.isSelectable(t)));\n if (next.size === 0)\n return;\n this.#anchor = [...next].at(-1);\n this.#apply(next);\n }\n\n /** Remove a single item from the selection. */\n deselect(item: T): void {\n if (!this.#selected.has(item))\n return;\n const next = new Set(this.#selected);\n next.delete(item);\n this.#apply(next);\n }\n\n // --- Gesture routers ---\n\n /** Route a click on `item` according to the current interaction mode. */\n handleClick(item: T, gesture: ListSelectionGesture): void {\n const { selectionMode, interactionMode: mode } = this.#config.getModes();\n if (mode === `manual`)\n return;\n if (selectionMode === `none` && mode !== `checked` && mode !== `sticky`)\n return;\n\n switch (mode) {\n case `implicit`:\n // Single-select only; modifier keys have no effect\n this.replace(item);\n break;\n\n case `standard`:\n case `vscode`:\n if (gesture.ctrlKey || gesture.metaKey) {\n // Ctrl/Cmd+click: toggle item; requires effective multi-select\n if (selectionMode === `multiple` || mode === `vscode`) {\n this.toggle(item);\n }\n } else if (gesture.shiftKey && (selectionMode === `multiple` || mode === `vscode`)) {\n // Shift+click: range-select from anchor\n this.rangeTo(item);\n } else {\n // Plain click: replace selection, update anchor\n this.replace(item);\n }\n break;\n\n case `checked`:\n // Body clicks do nothing — only the checkbox column toggles.\n break;\n\n case `sticky`:\n // Every tap/click toggles — multi-select without modifier keys (touchscreens).\n this.toggle(item);\n break;\n }\n }\n\n /** Route a Space keypress on `item` according to the current interaction mode. */\n handleSpace(item: T): void {\n const mode = this.#config.getModes().interactionMode;\n if (mode === `manual`)\n return;\n if (mode === `implicit`) {\n // Space selects (same as click) in implicit mode\n this.replace(item);\n } else {\n // standard / vscode / checked / sticky: Space toggles\n this.toggle(item);\n }\n }\n\n // --- Maintenance ---\n\n /** Drop the range-selection anchor (e.g. when the interaction mode changes). */\n clearAnchor(): void {\n this.#anchor = undefined;\n }\n\n /**\n * Re-key the selection across an identity boundary — e.g. when items are\n * wrapped/unwrapped and the selected Element references change. Silent:\n * no `onChange` is fired.\n */\n remap(fn: (item: T) => T): void {\n if (this.#selected.size === 0)\n return;\n this.#selected = new Set([...this.#selected].map(fn));\n }\n\n /**\n * Hard reset: empty the selection and clear the anchor without firing\n * `onChange` (e.g. when all items are removed).\n */\n reset(): void {\n this.#selected = new Set();\n this.#anchor = undefined;\n }\n\n /**\n * Replace the selection and anchor silently (no `onChange`). Used to\n * re-key the selection when items are rebuilt from data — e.g. matching\n * new elements to previously selected keys.\n */\n seed(items: Iterable<T>, anchor?: T): void {\n this.#selected = new Set(items);\n this.#anchor = anchor;\n }\n\n #apply(next: ReadonlySet<T>): void {\n const previous = this.#selected;\n this.#selected = next;\n this.#config.onChange(next, previous);\n }\n}\n","import type { CommandId } from '@clinth/ui-commands';\nimport type { PropertyValues } from 'lit';\nimport type { ElementIncrSearch } from '../incr-search/element-search.js';\nimport type {\n ListActivateDetail,\n ListElementEventMap,\n ListInteractionMode,\n ListItemClickDetail,\n ListSelectDetail,\n ListSelectionMode,\n ListTickleDetail,\n} from '../interaction/list-selection-types.js';\nimport { css, html, LitElement, render } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\nimport { createElementIncrSearch } from '../incr-search/element-search.js';\nimport { ListSelectionController } from '../interaction/list-selection-controller.js';\nimport { TickledController } from '../interaction/tickled-controller.js';\nimport { themeFallbacks } from '../styles/fallbacks.js';\nimport { safeCustomElement as customElement } from '../util/custom-elements.js';\n\nexport type {\n ListActivateDetail,\n ListActivateEvent,\n ListElementEventMap,\n ListInteractionMode,\n ListItemClickDetail,\n ListItemClickEvent,\n ListSelectDetail,\n ListSelectEvent,\n ListSelectionMode,\n ListTickleDetail,\n ListTickleEvent,\n} from './types.js';\n\nexport interface CustomCheckboxOptions {\n renderer: (checked: boolean) => unknown;\n margin?: string;\n width?: string;\n}\n\nexport interface AddItemOptions {\n /**\n * In `checked` interaction mode, adds the item without a checkbox column and\n * without the ability to be checked/selected. Equivalent to setting the\n * `not-checked` attribute on the item. Ignored outside `checked` mode.\n */\n notChecked?: boolean;\n}\n\nconst ITEM_SELECTOR = `li, [data-list-item]`;\n\n@customElement(`ixfx-vertical-list`)\nexport class VerticalListElement extends LitElement {\n @property({ type: String, attribute: `selection-mode` })\n selectionMode: ListSelectionMode = `single`;\n\n @property({ type: String, attribute: `interaction-mode` })\n interactionMode: ListInteractionMode = `standard`;\n\n @property({ attribute: false })\n filterPredicate: ((el: Element) => boolean) | undefined;\n\n /**\n * Whether keyboard navigation wraps from last to first and vice versa.\n * - 'stop' (default): navigation stops at the boundary\n * - 'wrap': navigation wraps around\n */\n @property({ type: String, attribute: `item-wrap` })\n itemWrap: `stop` | `wrap` = `stop`;\n\n /**\n * Optional renderer for the checkbox shown in `checked` interactionMode.\n * Receives the current `checked` state and returns a Lit template result.\n *\n * When set, the per-row checkbox becomes a `<span role=\"checkbox\">` whose\n * contents are whatever this returns (rendered with `lit`'s `render()`).\n * When unset, a native `<input type=\"checkbox\">` is used.\n *\n * @example\n * list.checkboxRenderer = (checked) => checked\n * ? html`<svg viewBox=\"0 0 16 16\" width=\"14\" height=\"14\"\n * fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n * stroke-linecap=\"round\" stroke-linejoin=\"round\"\n * style=\"opacity:0.5\">\n * <polyline points=\"3,8 7,12 13,4\" />\n * </svg>`\n * : html``;\n */\n @property({ attribute: false })\n checkboxRenderer: ((checked: boolean) => unknown) | CustomCheckboxOptions | undefined;\n\n @state() private _searchVisible = false;\n @state() private _searchQuery = ``;\n\n @query(`.search-input`)\n private _searchInputEl!: HTMLInputElement;\n\n #search: ElementIncrSearch | undefined;\n #customWrappers = new Map<HTMLLIElement, Element>();\n #wrappedElements = new Set<Element>();\n\n private readonly selection = new ListSelectionController<Element>({\n getOrderedItems: () => this._getVisibleItems(),\n isSelectable: el => this._isCheckable(el),\n getModes: () => ({ selectionMode: this.selectionMode, interactionMode: this.interactionMode }),\n onChange: (selected, previous) => {\n this._syncItemStates();\n this._dispatch<ListSelectDetail>(`list-select`, { selected, previous });\n },\n });\n\n private tickled = new TickledController<Element>(this, {\n getItems: () => this._getVisibleItems().map(el => ({\n id: el,\n enabled: !el.hasAttribute(`not-checked`),\n })),\n onTickle: (id) => {\n if (id !== undefined) {\n this._dispatch<ListTickleDetail>(`list-tickle`, { item: id });\n }\n },\n onStateChange: () => this._syncItemStates(),\n itemWrap: this.itemWrap,\n });\n\n override connectedCallback(): void {\n super.connectedCallback();\n if (!this.hasAttribute(`tabindex`))\n this.tabIndex = 0;\n this.addEventListener(`pointerover`, this._onPointerOver);\n this.addEventListener(`pointerleave`, this._onPointerLeave);\n this.addEventListener(`keydown`, this._onKeyDown);\n this.addEventListener(`click`, this._onClick);\n this.addEventListener(`dblclick`, this._onDblClick);\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n this.removeEventListener(`pointerover`, this._onPointerOver);\n this.removeEventListener(`pointerleave`, this._onPointerLeave);\n this.removeEventListener(`keydown`, this._onKeyDown);\n this.removeEventListener(`click`, this._onClick);\n this.removeEventListener(`dblclick`, this._onDblClick);\n this.#search?.dispose();\n this.#search = undefined;\n }\n\n override firstUpdated(): void {\n this.#search = createElementIncrSearch({\n container: this,\n itemSelector: ITEM_SELECTOR,\n getLabel: el => el.getAttribute(`data-search-label`) ?? el.textContent?.trim() ?? ``,\n getKey: el =>\n el.getAttribute(`data-key`)\n ?? el.getAttribute(`data-search-label`)\n ?? el.textContent?.trim()\n ?? ``,\n highlightKey: `vertical-list-search`,\n });\n }\n\n override updated(changed: PropertyValues): void {\n super.updated(changed);\n if (changed.has(`interactionMode`)) {\n this.selection.clearAnchor();\n const previousSelected = this.selection.selected;\n // Save wrapper→inner map before _syncCheckboxes() clears it on unwrap\n const leavingChecked = (changed.get(`interactionMode`) as ListInteractionMode) === `checked`;\n const savedWrappers = leavingChecked ? new Map(this.#customWrappers) : null;\n\n this._syncCheckboxes();\n\n // Remap the selection across the wrap/unwrap element-reference boundary\n if (previousSelected.size > 0) {\n if (savedWrappers && savedWrappers.size > 0) {\n // Leaving checked: wrapper refs → inner element refs\n this.selection.remap(el => savedWrappers.get(el as HTMLLIElement) ?? el);\n } else if (this.#customWrappers.size > 0) {\n // Entering checked: inner element refs → wrapper refs\n const innerToWrapper = new Map([...this.#customWrappers].map(([w, i]) => [i, w]));\n this.selection.remap(el => innerToWrapper.get(el) ?? el);\n }\n }\n\n this._syncItemStates();\n } else if (changed.has(`checkboxRenderer`) && this.interactionMode === `checked`) {\n this._syncCheckboxes();\n }\n }\n\n // --- Public API ---\n\n /** The most recently selected item, or `undefined` if nothing is selected. */\n get selectedItem(): Element | undefined {\n return [...this.selection.selected].at(-1);\n }\n\n /** The full current selection set (read-only). */\n get selectedItems(): ReadonlySet<Element> {\n return this.selection.selected;\n }\n\n addItem(item: Element | string, options?: AddItemOptions): void {\n let el: Element;\n if (typeof item === `string`) {\n const li = document.createElement(`li`);\n li.textContent = item;\n el = li;\n } else {\n el = item;\n }\n if (options?.notChecked)\n el.setAttribute(`not-checked`, ``);\n this.appendChild(el);\n if (this.interactionMode === `checked`)\n this._syncCheckboxes();\n }\n\n removeItem(item: Element): void {\n item.remove();\n }\n\n clearItems(): void {\n this.#customWrappers.clear();\n this.replaceChildren();\n this.selection.reset();\n this._syncItemStates();\n }\n\n /**\n * Replace the entire item list in one call.\n * Equivalent to `clearItems()` followed by `addItem()` for each element.\n * Preserves `interactionMode` checkbox wrapping.\n */\n setItems(items: readonly Element[]): void {\n this.clearItems();\n for (const item of items) {\n this.appendChild(item);\n }\n if (this.interactionMode === `checked`)\n this._syncCheckboxes();\n }\n\n /**\n * Dispatch a command to this list. No-op by default.\n * Intended for use with `ixfx-grouped-item-lister`'s `dispatch()` method,\n * which forwards commands to all sub-components.\n */\n dispatch(_commandId: CommandId, _args?: Record<string, unknown>): void {}\n\n select(item: Element): void {\n this.selection.replace(item);\n }\n\n selectMany(items: Iterable<Element>): void {\n this.selection.selectMany(items);\n }\n\n deselect(item: Element): void {\n this.selection.deselect(item);\n }\n\n clearSelection(): void {\n this.selection.clear();\n }\n\n selectAll(): void {\n if (this.selection.canSelectAll())\n this.selection.selectAll();\n }\n\n * selectedFilter(kind: `selected` | `checked` | `either`): Generator<Element> {\n const wanted = (el: Element) => {\n switch (kind) {\n case `selected`: return el.hasAttribute(`data-selected`);\n case `checked`: return el.hasAttribute(`data-checked`);\n case `either`: return el.hasAttribute(`data-selected`) || el.hasAttribute(`data-checked`);\n }\n };\n\n for (const el of this.querySelectorAll(ITEM_SELECTOR)) {\n if (this.#wrappedElements.has(el))\n continue;\n if ((el as HTMLElement).hidden)\n continue;\n if (wanted(el))\n yield el;\n }\n }\n\n // --- Helpers ---\n\n private _getVisibleItems(): Element[] {\n return Array.from(this.querySelectorAll(ITEM_SELECTOR)).filter((el) => {\n if (this.#wrappedElements.has(el))\n return false;\n if ((el as HTMLElement).hidden)\n return false;\n if (this.filterPredicate && !this.filterPredicate(el))\n return false;\n return true;\n });\n }\n\n // Whether an item participates in checked-mode selection. Items carrying the\n // `not-checked` attribute are skipped; outside `checked` mode it has no effect.\n // For wrapped custom elements the flag lives on the inner element, so resolve\n // through the wrapper map.\n private _isCheckable(item: Element): boolean {\n if (this.interactionMode !== `checked`)\n return true;\n const real = this.#customWrappers.get(item as HTMLLIElement) ?? item;\n return !real.hasAttribute(`not-checked`);\n }\n\n private _syncItemStates(): void {\n for (const el of this.querySelectorAll(ITEM_SELECTOR)) {\n if (this.#wrappedElements.has(el))\n continue;\n const item = this.#customWrappers.get(el as HTMLLIElement) ?? el;\n el.toggleAttribute(`data-tickled`, this.tickled.isTickled(el));\n el.toggleAttribute(`data-selected`, this.selection.selected.has(el));\n if (this.interactionMode === `checked`) {\n el.toggleAttribute(`data-checked`, !item.hasAttribute(`not-checked`) && this.selection.selected.has(el));\n } else {\n el.removeAttribute(`data-checked`);\n }\n }\n this.dataset.inputMode = this.tickled.inputMode;\n this._syncCheckboxes();\n }\n\n private _syncCheckboxes(): void {\n const inCheckedMode = this.interactionMode === `checked`;\n const wantsCustom = typeof this.checkboxRenderer === `function`;\n\n if (inCheckedMode) {\n for (const inner of Array.from(this.children)) {\n if (!inner.hasAttribute(`data-list-item`))\n continue;\n if (inner.parentElement !== this)\n continue; // idempotency: already wrapped\n const wrapper = document.createElement(`li`);\n wrapper.className = `ixfx-custom-row`;\n const key = inner.getAttribute(`data-key`);\n const label = inner.getAttribute(`data-search-label`);\n if (key !== null)\n wrapper.setAttribute(`data-key`, key);\n if (label !== null)\n wrapper.setAttribute(`data-search-label`, label);\n inner.removeAttribute(`data-list-item`);\n this.insertBefore(wrapper, inner);\n wrapper.appendChild(inner);\n this.#customWrappers.set(wrapper, inner);\n this.#wrappedElements.add(inner);\n }\n } else {\n for (const [wrapper, inner] of this.#customWrappers) {\n if (wrapper.parentElement !== this)\n continue;\n inner.setAttribute(`data-list-item`, ``);\n this.insertBefore(inner, wrapper);\n wrapper.remove();\n }\n this.#customWrappers.clear();\n this.#wrappedElements.clear();\n }\n\n for (const el of this.querySelectorAll<HTMLElement>(`li`)) {\n const existing = el.querySelector<HTMLElement>(`.ixfx-list-checkbox`);\n if (!inCheckedMode) {\n if (existing)\n existing.remove();\n continue;\n }\n // Rows marked `not-checked` get no checkbox column and can't be checked\n const itemEl = this.#customWrappers.get(el as HTMLLIElement) ?? el;\n if (itemEl.hasAttribute(`not-checked`)) {\n if (existing)\n existing.remove();\n continue;\n }\n\n // In checked mode: if the existing checkbox is the wrong variant, drop it.\n const existingIsCustom = existing?.classList.contains(`ixfx-list-checkbox-custom`) ?? false;\n if (existing && existingIsCustom !== wantsCustom)\n existing.remove();\n\n let cb: HTMLElement | null = el.querySelector<HTMLElement>(`.ixfx-list-checkbox`);\n if (!cb) {\n if (wantsCustom) {\n const opts = this.checkboxRenderer as CustomCheckboxOptions;\n const margin = opts.margin ?? `0`;\n const width = opts.width ?? `24px`;\n cb = this._createCustomCheckbox(el, margin, width);\n } else {\n cb = this._createNativeCheckboxCell(el);\n }\n el.prepend(cb);\n }\n\n const checked = this.selection.selected.has(el);\n if (wantsCustom) {\n cb.setAttribute(`aria-checked`, String(checked));\n cb.toggleAttribute(`data-checked`, checked);\n if (this.checkboxRenderer) {\n const renderer = typeof this.checkboxRenderer === `function`\n ? this.checkboxRenderer\n : (this.checkboxRenderer as CustomCheckboxOptions).renderer;\n render(renderer(checked), cb);\n }\n } else {\n const input = cb.querySelector<HTMLInputElement>(`input.ixfx-list-checkbox`);\n if (input)\n input.checked = checked;\n }\n }\n }\n\n /**\n * Native checkbox cell: a span that stretches to the row's full cross-axis\n * (so the entire column height is a click target) with a real `<input>`\n * centred inside. The span owns the click; the input is purely visual and\n * has `pointer-events:none` so a single click produces one toggle.\n */\n private _createNativeCheckboxCell(el: HTMLElement): HTMLSpanElement {\n const cell = document.createElement(`span`);\n cell.className = `ixfx-list-checkbox ixfx-list-checkbox-native`;\n cell.style.cssText = `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;align-self:stretch;width:18px;margin:0 6px 0 0;cursor:pointer;pointer-events:auto`;\n cell.tabIndex = -1;\n cell.setAttribute(`role`, `checkbox`);\n cell.setAttribute(`part`, `checkbox`);\n cell.addEventListener(`click`, (e) => {\n e.stopPropagation();\n this.selection.toggle(el);\n });\n const input = document.createElement(`input`);\n input.type = `checkbox`;\n input.className = `ixfx-list-checkbox`;\n input.style.cssText = `pointer-events:none;cursor:pointer;margin:0`;\n cell.appendChild(input);\n return cell;\n }\n\n /**\n * Custom checkbox cell: same shape as the native cell (a stretching span)\n * so behaviour is identical. The renderer's content goes inside.\n */\n private _createCustomCheckbox(el: HTMLElement, margin: string, width: string): HTMLSpanElement {\n const span = document.createElement(`span`);\n span.className = `ixfx-list-checkbox ixfx-list-checkbox-custom`;\n span.style.cssText = `display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;align-self:stretch;width:${width};margin:${margin};cursor:pointer;pointer-events:auto`;\n span.tabIndex = -1;\n span.setAttribute(`role`, `checkbox`);\n span.setAttribute(`part`, `checkbox`);\n span.addEventListener(`click`, (e) => {\n e.stopPropagation();\n this.selection.toggle(el);\n });\n return span;\n }\n\n private _findListItem(path: EventTarget[]): Element | undefined {\n for (const target of path) {\n if (!(target instanceof Element))\n continue;\n if (target === this)\n break;\n if (target.parentElement === this && target.matches(ITEM_SELECTOR))\n return target;\n }\n return undefined;\n }\n\n private _dispatch<T>(name: keyof ListElementEventMap, detail: T): void {\n this.dispatchEvent(new CustomEvent<T>(name as string, {\n detail,\n bubbles: true,\n composed: true,\n }));\n }\n\n private _openSearch(): void {\n this._searchVisible = true;\n void this.updateComplete.then(() => this._searchInputEl?.focus());\n }\n\n private _closeSearch(): void {\n this._searchVisible = false;\n this._searchQuery = ``;\n void this.#search?.clearQuery();\n this.focus();\n }\n\n // --- Event handlers ---\n\n private _onPointerOver = (e: PointerEvent): void => {\n const item = this._findListItem(e.composedPath());\n if (item)\n this.tickled.handlePointerEnter(item);\n };\n\n private _onPointerLeave = (e: PointerEvent): void => {\n this.tickled.handlePointerLeave(e);\n };\n\n private _onKeyDown = (e: KeyboardEvent): void => {\n if ((e.ctrlKey || e.metaKey) && e.key === `f`) {\n e.preventDefault();\n this._openSearch();\n return;\n }\n if ((e.ctrlKey || e.metaKey) && e.key === `a`) {\n if (this.selection.canSelectAll()) {\n e.preventDefault();\n this.selection.selectAll();\n }\n return;\n }\n if (this._searchVisible)\n return;\n\n switch (e.key) {\n case `ArrowDown`: {\n e.preventDefault();\n this.tickled.handleKeyboardNavigation(`next`);\n // vscode: Shift+Arrow extends range from anchor\n if (e.shiftKey && this.interactionMode === `vscode`) {\n const cur = this.tickled.tickledId;\n if (cur)\n this.selection.rangeTo(cur);\n }\n break;\n }\n case `ArrowUp`: {\n e.preventDefault();\n this.tickled.handleKeyboardNavigation(`previous`);\n if (e.shiftKey && this.interactionMode === `vscode`) {\n const cur = this.tickled.tickledId;\n if (cur)\n this.selection.rangeTo(cur);\n }\n break;\n }\n case `Enter`: {\n const id = this.tickled.tickledId;\n if (id) {\n e.preventDefault();\n this._dispatch<ListItemClickDetail>(`list-item-click`, { item: id });\n this._activateItem(id);\n }\n break;\n }\n case ` `: {\n const id = this.tickled.tickledId;\n if (id) {\n e.preventDefault();\n this.selection.handleSpace(id);\n }\n break;\n }\n case `Escape`:\n this.selection.clear();\n break;\n }\n };\n\n private _onClick = (e: MouseEvent): void => {\n const item = this._findListItem(e.composedPath());\n if (!item)\n return;\n this._dispatch<ListItemClickDetail>(`list-item-click`, { item });\n this.selection.handleClick(item, e);\n };\n\n private _onDblClick = (e: MouseEvent): void => {\n const item = this._findListItem(e.composedPath());\n if (item)\n this._activateItem(item);\n };\n\n private _activateItem(item: Element): void {\n // manual mode: never auto-select; just fire the event\n if (this.interactionMode !== `manual`) {\n this.selection.replace(item);\n }\n this._dispatch<ListActivateDetail>(`list-activate`, { item });\n }\n\n private _onSearchInput = (e: Event): void => {\n const input = e.target as HTMLInputElement;\n this._searchQuery = input.value;\n void this.#search?.setQuery(this._searchQuery);\n };\n\n private _onSearchKeydown = (e: KeyboardEvent): void => {\n if (e.key === `Escape`) {\n e.stopPropagation();\n this._closeSearch();\n } else if (e.key === `ArrowDown`) {\n e.preventDefault();\n this.tickled.handleKeyboardNavigation(`next`);\n } else if (e.key === `ArrowUp`) {\n e.preventDefault();\n this.tickled.handleKeyboardNavigation(`previous`);\n } else if (e.key === `Enter`) {\n e.preventDefault();\n e.stopPropagation();\n const id = this.tickled.tickledId ?? this._getVisibleItems()[0];\n if (id) {\n this._dispatch<ListItemClickDetail>(`list-item-click`, { item: id });\n this._activateItem(id);\n this._closeSearch();\n }\n }\n };\n\n // --- Styles ---\n\n static override styles = [themeFallbacks, css`\n :host {\n display: block;\n position: relative;\n background: var(--list-bg, var(--surface-3));\n color: var(--list-text, var(--surface-3-text));\n border-radius: var(--list-radius, var(--radius-s));\n border: 1px solid var(--list-border, var(--border));\n width: 100%;\n height: 100%;\n overflow: hidden;\n box-sizing: border-box;\n outline: none;\n --ixfx-list-item-padding: var(--ixfx-list-item-padding, var(--list-item-padding, 4px var(--space-m, 8px)));\n }\n\n :host(:focus-visible) {\n outline: 2px solid var(--accent);\n outline-offset: 2px;\n }\n\n .container {\n position: relative;\n width: 100%;\n height: 100%;\n }\n\n ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n height: 100%;\n overflow-x: hidden;\n overflow-y: auto;\n box-sizing: border-box;\n /* Scrollbar theming: end users override these CSS variables to control\n the native scrollbar (scrollbar-width, scrollbar-gutter, scrollbar-color). */\n scrollbar-width: var(--list-scrollbar-width, auto);\n scrollbar-gutter: var(--list-scrollbar-gutter, auto);\n scrollbar-color: var(--list-scrollbar-color, auto);\n }\n\n ::slotted([hidden]) {\n display: none !important;\n }\n\n ::slotted(li) {\n display: flex;\n align-items: center;\n padding: var(--ixfx-list-item-padding);\n min-height: var(--list-item-height, 28px);\n cursor: pointer;\n user-select: none;\n transition: background var(--transition, 0.1s ease-out),\n outline-color var(--transition, 0.1s ease-out);\n box-sizing: border-box;\n outline: 1px solid transparent;\n }\n\n /* Custom shadow-DOM components handle their own internal layout/padding */\n ::slotted([data-list-item]) {\n display: block;\n cursor: pointer;\n user-select: none;\n transition: background var(--transition, 0.1s ease-out),\n outline-color var(--transition, 0.1s ease-out);\n outline: 1px solid transparent;\n }\n\n /* In checked mode, only the checkbox column is clickable; the row body\n gets a default cursor so it doesn't suggest otherwise. */\n :host([interaction-mode=\"checked\"]) ::slotted(li),\n :host([interaction-mode=\"checked\"]) ::slotted([data-list-item]) {\n cursor: default;\n }\n\n /* Wrapper inserted around custom [data-list-item] elements in checked mode.\n Provides the flex layout so the injected checkbox can stretch to the\n row's full height via align-self:stretch. */\n .ixfx-custom-row {\n display: flex;\n align-items: center;\n min-height: var(--list-item-height, 28px);\n box-sizing: border-box;\n cursor: default;\n user-select: none;\n }\n\n ::slotted([data-tickled]) {\n background: var(--item-bg-tickled);\n outline: var(--item-border-tickled);\n }\n\n ::slotted([data-selected]) {\n background: var(--item-bg-selected);\n color: var(--item-text-selected);\n outline: 1px solid transparent;\n }\n\n ::slotted([data-selected][data-tickled]) {\n background: var(--item-bg-selected-tickled);\n outline: 2px dashed var(--surface-3);\n outline-offset: -2px;\n }\n\n /* checked mode: checkbox (injected into li) communicates selection — suppress bg highlight */\n :host([interaction-mode=\"checked\"]) ::slotted([data-selected]) {\n background: transparent;\n color: inherit;\n outline: 1px solid transparent;\n }\n\n :host([interaction-mode=\"checked\"]) ::slotted([data-selected][data-tickled]) {\n background: var(--item-bg-tickled);\n color: inherit;\n outline: var(--item-border-tickled);\n }\n\n .search-overlay {\n position: absolute;\n top: 8px;\n right: 8px;\n z-index: 10;\n display: flex;\n align-items: center;\n gap: 4px;\n background: var(--surface-4, #2a2a2a);\n border: 1px solid var(--border, #444);\n border-radius: var(--radius-m, 4px);\n padding: 4px 6px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);\n }\n\n .search-overlay[hidden] {\n display: none;\n }\n\n .search-input {\n background: transparent;\n border: none;\n outline: none;\n color: inherit;\n font-size: var(--text-m, 0.875em);\n min-width: 140px;\n }\n\n .search-close {\n background: transparent;\n border: none;\n color: var(--surface-muted-text, #888);\n cursor: pointer;\n font-size: 1.1em;\n line-height: 1;\n padding: 0 2px;\n }\n\n .search-close:hover {\n color: inherit;\n }\n `];\n\n // --- Render ---\n\n override render() {\n return html`\n <div class=\"container\">\n <ul part=\"list\">\n <slot></slot>\n </ul>\n <div\n class=\"search-overlay\"\n part=\"search-overlay\"\n ?hidden=${!this._searchVisible}\n >\n <input\n type=\"text\"\n class=\"search-input\"\n placeholder=\"Search…\"\n .value=${this._searchQuery}\n @input=${this._onSearchInput}\n @keydown=${this._onSearchKeydown}\n />\n <button class=\"search-close\" @click=${this._closeSearch} title=\"Close search (Esc)\">×</button>\n </div>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ixfx-vertical-list': VerticalListElement;\n }\n\n interface HTMLElementEventMap extends ListElementEventMap {}\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { t as VerticalListElement } from "./vertical-list-ClnqTkF3.js";
1
+ import { t as VerticalListElement } from "./vertical-list-B4w_ksJk.js";
2
2
  export { VerticalListElement };
@@ -53,5 +53,6 @@ This directory contains documentation for each component and some architectural
53
53
 
54
54
  ## Architectural Overviews
55
55
 
56
+ - [infra-tickled.md](infra-tickled.md)
56
57
  - [user-catalog.md](user-catalog.md)
57
58
  - [user-theming.md](user-theming.md)
@@ -8,20 +8,35 @@ Selection semantics match `ixfx-vertical-list` — same modes, same `list-*` eve
8
8
 
9
9
  ## Contents
10
10
 
11
- 1. [Quick start](#quick-start)
12
- 2. [Item types](#item-types)
13
- 3. [Properties](#properties)
14
- 4. [Selection](#selection)
15
- 5. [Interaction modes](#interaction-modes)
16
- 6. [Custom checkbox](#custom-checkbox)
17
- 7. [Events](#events)
18
- 8. [Keyboard navigation](#keyboard-navigation)
19
- 9. [Incremental search](#incremental-search)
20
- 10. [Filter predicate](#filter-predicate)
21
- 11. [Sorting](#sorting)
22
- 12. [Asynchronous data](#asynchronous-data)
23
- 13. [CSS variables](#css-variables)
24
- 14. [CSS parts](#css-parts)
11
+ - [`ixfx-detail-list`](#ixfx-detail-list)
12
+ - [Contents](#contents)
13
+ - [Quick start](#quick-start)
14
+ - [Declarative HTML items](#declarative-html-items)
15
+ - [Programmatic items](#programmatic-items)
16
+ - [Item types](#item-types)
17
+ - [Plain `<li>` elements](#plain-li-elements)
18
+ - [Object items](#object-items)
19
+ - [Properties](#properties)
20
+ - [Selection](#selection)
21
+ - [Getters](#getters)
22
+ - [Methods](#methods)
23
+ - [Interaction modes](#interaction-modes)
24
+ - [Custom checkbox](#custom-checkbox)
25
+ - [Events](#events)
26
+ - [Keyboard navigation](#keyboard-navigation)
27
+ - [Wrapping](#wrapping)
28
+ - [Incremental search](#incremental-search)
29
+ - [Highlight styling](#highlight-styling)
30
+ - [Filter predicate](#filter-predicate)
31
+ - [Sorting](#sorting)
32
+ - [Simple sort](#simple-sort)
33
+ - [Sort by property](#sort-by-property)
34
+ - [Custom comparator](#custom-comparator)
35
+ - [Asynchronous data](#asynchronous-data)
36
+ - [Promise provider](#promise-provider)
37
+ - [Streaming provider](#streaming-provider)
38
+ - [CSS variables](#css-variables)
39
+ - [CSS parts](#css-parts)
25
40
 
26
41
  ---
27
42
 
@@ -373,13 +388,25 @@ Streaming providers extend columns to the right as batches arrive.
373
388
  | `--detail-list-column-width` | `220px` | Width of each column |
374
389
  | `--detail-list-column-gap` | `8px` | Gap between columns |
375
390
  | `--detail-list-item-height` | `28px` | Height of each item |
376
- | `--detail-list-item-padding` | `0 var(--space-m)` | Item padding |
391
+ | `--detail-list-item-padding` | `0 var(--space-xs)` | Item padding |
377
392
  | `--detail-list-scrollbar-width` | `auto` | Scrollbar width |
378
393
  | `--detail-list-scrollbar-gutter` | `auto` | Scrollbar gutter |
379
394
  | `--detail-list-scrollbar-color` | `auto` | Scrollbar color |
380
395
 
381
396
  Plus all `--item-bg-*`, `--item-text-*` variables from the theme for selection/tickle states.
382
397
 
398
+ To explicitly size columns to the widest visible item, call `fitColumnWidth()`. It
399
+ returns the applied width in pixels and does not run automatically:
400
+
401
+ ```ts
402
+ const width = await list.fitColumnWidth();
403
+ console.log(`Column width: ${width}px`);
404
+ ```
405
+
406
+ The resulting pixel value is set on the element itself and overrides an inherited
407
+ `--detail-list-column-width` value. Lists otherwise continue to use the configured
408
+ CSS variable or the default `220px` width.
409
+
383
410
  ---
384
411
 
385
412
  ## CSS parts
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
- "version": "0.7.2",
4
- "generated": "2026-09-06T14:04:51.476Z",
3
+ "version": "0.7.3",
4
+ "generated": "2026-09-06T18:27:10.873Z",
5
5
  "components": [
6
6
  {
7
7
  "name": "ac-text",
@@ -235,6 +235,10 @@
235
235
  }
236
236
  ],
237
237
  "overviews": [
238
+ {
239
+ "name": "infra-tickled.md",
240
+ "path": "docs-user/infra-tickled.md"
241
+ },
238
242
  {
239
243
  "name": "user-catalog.md",
240
244
  "path": "docs-user/user-catalog.md"
@@ -0,0 +1,163 @@
1
+ # Tickled items
2
+
3
+ An item is **tickled** when it is under active attention — pointer hovering over it, or keyboard cursor on it. Only one item is tickled at a time — a single logical cursor regardless of input mechanism.
4
+
5
+ **Tickled state is controlled via the `.tickled` CSS class** (applied by `TickledController`). The `tickledItemStyles` provides base styling that all tickled components should use.
6
+
7
+ ---
8
+
9
+ ## TickledController
10
+
11
+ `TickledController` is a Lit `ReactiveController` from `src/interaction/index.js` that manages this state for you.
12
+
13
+ ### Direct-render setup (items in your template)
14
+
15
+ ```ts
16
+ import { TickledController, tickledStyles, tickledItemStyles } from '../interaction/index.js';
17
+
18
+ class MyList extends LitElement {
19
+ static override styles = [
20
+ tickledStyles,
21
+ tickledItemStyles,
22
+ css`
23
+ .item.selected { background: var(--item-bg-selected); }
24
+ `
25
+ ];
26
+
27
+ private tickled = new TickledController(this, {
28
+ getItems: () => this.items.map((_, i) => ({ id: i })),
29
+ });
30
+
31
+ protected override render() {
32
+ return html`
33
+ <div
34
+ data-input-mode=${this.tickled.inputMode}
35
+ @pointerleave=${() => this.tickled.clearTickled()}
36
+ @keydown=${this.#handleKeyDown}
37
+ >
38
+ ${this.items.map((item, i) => html`
39
+ <div
40
+ class="item ${this.tickled.isTickled(i) ? 'tickled' : ''}"
41
+ @pointerenter=${() => this.tickled.handlePointerEnter(i)}
42
+ >${item}</div>
43
+ `)}
44
+ </div>
45
+ `;
46
+ }
47
+
48
+ #handleKeyDown(e: KeyboardEvent) {
49
+ if (e.key === 'ArrowDown') { e.preventDefault(); this.tickled.handleKeyboardNavigation('next'); }
50
+ else if (e.key === 'ArrowUp') { e.preventDefault(); this.tickled.handleKeyboardNavigation('previous'); }
51
+ else if (e.key === 'Home') { e.preventDefault(); this.tickled.handleKeyboardNavigation('first'); }
52
+ else if (e.key === 'End') { e.preventDefault(); this.tickled.handleKeyboardNavigation('last'); }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### Slotted-content setup
58
+
59
+ Lit's reactive rendering doesn't touch slotted elements, so you must sync DOM classes manually via `onStateChange`.
60
+
61
+ ```ts
62
+ private tickled = new TickledController<number>(this, {
63
+ getItems: () => this._slottedItems().map((_, i) => ({ id: i })),
64
+ onStateChange: () => this._syncTickled(),
65
+ });
66
+
67
+ // Must run in willUpdate so :host gets data-input-mode before paint
68
+ override willUpdate(changed: Map<PropertyKey, unknown>) {
69
+ super.willUpdate(changed);
70
+ this.setAttribute('data-input-mode', this.tickled.inputMode);
71
+ }
72
+
73
+ private _slottedItems(): HTMLElement[] {
74
+ const slot = this.shadowRoot?.querySelector('slot');
75
+ return Array.from(slot?.assignedElements() ?? []) as HTMLElement[];
76
+ }
77
+
78
+ private _syncTickled() {
79
+ this._slottedItems().forEach((item, i) => {
80
+ const isTickled = this.tickled.isTickled(i);
81
+ if (isTickled && this.tickled.inputMode === 'keyboard') {
82
+ item.classList.add('tickled');
83
+ } else {
84
+ item.classList.remove('tickled');
85
+ }
86
+ item.tabIndex = isTickled ? 0 : -1;
87
+ });
88
+ }
89
+ ```
90
+
91
+ For slotted content, set `data-input-mode` on `:host` (not on an inner div) so `tickledStyles` CSS variables cascade through the shadow boundary to slotted elements.
92
+
93
+ ---
94
+
95
+ ## CSS variable contract
96
+
97
+ All `--item-*` state variables are defined in `src/style.css` (the theme). `tickledItemStyles` applies them to `.item` elements — it no longer sets defaults of its own for the colour/state variables.
98
+
99
+ See [infra-theming.md](./infra-theming.md#item-states) for the full variable listing and their theme values.
100
+
101
+ The layout variables (`--item-padding`, `--item-border-radius`, `--item-icon-spacing`, `--item-bg`) are also defined in `style.css` with defaults derived from the spacing/radius tokens.
102
+
103
+ ## Base item styles
104
+
105
+ `tickledItemStyles` provides these base styles:
106
+
107
+ ```css
108
+ .item {
109
+ display: inline-flex;
110
+ align-items: center;
111
+ gap: var(--item-icon-spacing);
112
+ padding: var(--item-padding);
113
+ background: var(--item-bg);
114
+ border: var(--item-border);
115
+ border-radius: var(--item-border-radius);
116
+ transition: background var(--transition), border-color var(--transition);
117
+ }
118
+
119
+ .item.tickled {
120
+ background: var(--item-bg-tickled);
121
+ border-color: var(--item-border-tickled);
122
+ color: var(--item-text-tickled);
123
+ }
124
+
125
+ .item.selected {
126
+ background: var(--item-bg-selected);
127
+ border-color: var(--item-border-selected);
128
+ color: var(--item-text-selected);
129
+ }
130
+
131
+ .item.selected.tickled {
132
+ background: var(--item-bg-selected-tickled);
133
+ outline: 2px dashed var(--surface-3);
134
+ outline-offset: -2px;
135
+ }
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Controller API
141
+
142
+ **Constructor:** `new TickledController<T>(host, config)`
143
+
144
+ Config:
145
+ - `getItems: () => readonly { id: T; enabled?: boolean }[]` — called on each render
146
+ - `onStateChange?: () => void` — fired after every state mutation (required for slotted content)
147
+ - `onTickle?: (id: T | undefined) => void` — called only when the tickled id *changes* (useful for opening submenus)
148
+ - `initialInputMode?: 'keyboard' | 'mouse'`
149
+
150
+ Properties: `inputMode`, `tickledId`
151
+
152
+ Methods: `isTickled(id)`, `handlePointerEnter(id)`, `handleKeyboardNavigation('next'|'previous'|'first'|'last'|T)`, `clearTickled()`, `setTickled(id)`, `setInputMode(mode)`
153
+
154
+ ---
155
+
156
+ ## Checklist
157
+
158
+ - [ ] `tickledStyles` and `tickledItemStyles` included in `static styles`
159
+ - [ ] Components use `tickledItemStyles` base styles, override only for component-specific needs
160
+ - [ ] `data-input-mode` set on the correct element (container div for direct-render; `:host` via `willUpdate` for slotted)
161
+ - [ ] `@pointerenter` on each item, `@pointerleave` on container
162
+ - [ ] Keyboard navigation calls `handleKeyboardNavigation`
163
+ - [ ] Slotted components: `onStateChange` + `_syncTickled()` implemented; `.tickled` class only added in keyboard mode
@@ -59,5 +59,6 @@ import '@ixfx/components/data-grid';
59
59
  - [xy-pad](docs-user/xy-pad.md): A 2D touch-friendly XY pad for controlling two parameters simultaneously. Each axis supports independent linear/logarithmic scaling.
60
60
 
61
61
  ## Overviews
62
+ - [infra-tickled.md](docs-user/infra-tickled.md)
62
63
  - [user-catalog.md](docs-user/user-catalog.md)
63
64
  - [user-theming.md](docs-user/user-theming.md)
package/llms.txt CHANGED
@@ -59,5 +59,6 @@ import '@ixfx/components/data-grid';
59
59
  - [xy-pad](docs-user/xy-pad.md): A 2D touch-friendly XY pad for controlling two parameters simultaneously. Each axis supports independent linear/logarithmic scaling.
60
60
 
61
61
  ## Overviews
62
+ - [infra-tickled.md](docs-user/infra-tickled.md)
62
63
  - [user-catalog.md](docs-user/user-catalog.md)
63
64
  - [user-theming.md](docs-user/user-theming.md)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixfx/components",
3
3
  "type": "module",
4
- "version": "0.7.2",
4
+ "version": "0.7.3",
5
5
  "packageManager": "pnpm@10.0.0",
6
6
  "description": "Web components for ixfx — Lit-based UI toolkit",
7
7
  "author": "Clint Heyer",
@@ -1 +0,0 @@
1
- {"version":3,"file":"icon-CDGprSdE.d.ts","names":[],"sources":["../src/icons/icon.ts"],"mappings":";;cAUa,wBAAwB;;EACvB;EACgB;EAChB;EAC0C;EACU;EAevD;EAMA;YAMU,iBAAM,wBAAA;YA4BN,QAAQ,SAAS;SAWpB,sBAAM;;QAiDhB;YACI;IACR,aAAa"}