@kodaris/krubble-components 1.0.53 → 1.0.54

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.
@@ -198,6 +198,14 @@
198
198
  "name": "KRAutoSuggest",
199
199
  "module": "./form/index.js"
200
200
  }
201
+ },
202
+ {
203
+ "kind": "js",
204
+ "name": "KRComboBox",
205
+ "declaration": {
206
+ "name": "KRComboBox",
207
+ "module": "./form/index.js"
208
+ }
201
209
  }
202
210
  ]
203
211
  },
@@ -565,7 +573,7 @@
565
573
  {
566
574
  "kind": "variable",
567
575
  "name": "KRTable",
568
- "default": "class KRTable extends i$2 { constructor() { super(...arguments); /** * Internal flag to switch between scroll edge modes: * - 'overlay': Fixed padding with overlay elements that hide content at edges (scrollbar at viewport edge) * - 'edge': Padding scrolls with content, allowing table to reach edges when scrolling */ this._scrollStyle = 'overlay'; this._data = []; this._dataState = 'idle'; this._page = 1; this._pageSize = 50; this._totalItems = 0; this._totalPages = 0; this._searchQuery = ''; this._canScrollLeft = false; this._canScrollRight = false; this._canScrollHorizontal = false; this._columnPickerOpen = false; this._filterPanelOpened = null; this._filterPanelTab = 'filter'; this._buckets = new Map(); this._filterPanelPos = { top: 0, left: 0 }; this._resizing = null; this._resizeObserver = null; this._searchPositionLocked = false; this._model = new KRTableModel(); this.def = { columns: [] }; this._handleClickOutside = (e) => { const path = e.composedPath(); if (this._columnPickerOpen) { const picker = this.shadowRoot?.querySelector('.column-picker-wrapper'); if (picker && !path.includes(picker)) { this._columnPickerOpen = false; } } if (this._filterPanelOpened) { if (!path.some((el) => el.classList?.contains('filter-panel'))) { this._handleFilterApply(); } } }; this._handleResizeMove = (e) => { if (!this._resizing) return; const col = this._model.columns.find(c => c.id === this._resizing.columnId); if (col) { const newWidth = this._resizing.startWidth + (e.clientX - this._resizing.startX); col.width = `${Math.min(900, Math.max(50, newWidth))}px`; this.requestUpdate(); } }; this._handleResizeEnd = () => { this._resizing = null; document.removeEventListener('mousemove', this._handleResizeMove); document.removeEventListener('mouseup', this._handleResizeEnd); }; } connectedCallback() { super.connectedCallback(); this.classList.toggle('kr-table--scroll-overlay', this._scrollStyle === 'overlay'); this.classList.toggle('kr-table--scroll-edge', this._scrollStyle === 'edge'); this._fetch(); this._initRefresh(); document.addEventListener('click', this._handleClickOutside); this._resizeObserver = new ResizeObserver(() => { // Unlock and recalculate on resize since layout changes this._searchPositionLocked = false; this._updateSearchPosition(); }); this._resizeObserver.observe(this); } disconnectedCallback() { super.disconnectedCallback(); clearInterval(this._refreshTimer); document.removeEventListener('click', this._handleClickOutside); this._resizeObserver?.disconnect(); } willUpdate(changedProperties) { if (changedProperties.has('def')) { // Build internal model from user-provided def this._model = new KRTableModel(); if (this.def.title) { this._model.title = this.def.title; } if (this.def.actions) { this._model.actions = this.def.actions; } if (this.def.data) { this._model.data = this.def.data; } if (this.def.dataSource) { this._model.dataSource = this.def.dataSource; } if (typeof this.def.refreshInterval === 'number') { this._model.refreshInterval = this.def.refreshInterval; } if (typeof this.def.pageSize === 'number') { this._model.pageSize = this.def.pageSize; } if (this.def.rowClickable) { this._model.rowClickable = this.def.rowClickable; } if (this.def.rowHref) { this._model.rowHref = this.def.rowHref; } this._model.columns = this.def.columns.map(col => { const column = { ...col, filter: null }; if (!column.type) { column.type = 'string'; } if (column.type === 'actions') { column.label = col.label ?? ''; column.sticky = 'right'; column.resizable = false; return column; } if (col.filterable || col.facetable) { column.filter = new KRQuery(); column.filter.field = col.id; column.filter.type = column.type; if (col.facetable && !col.filterable) { column.filter.operator = 'in'; column.filter.value = []; } else if (column.filter.type === 'string') { column.filter.operator = 'contains'; } } return column; }); if (this.def.displayedColumns) { this._model.displayedColumns = this.def.displayedColumns; } else { this._model.displayedColumns = this._model.columns.map(c => c.id); } this._fetch(); this._initRefresh(); } } updated(changedProperties) { this._updateScrollFlags(); this._syncSlottedContent(); } /** Syncs light DOM content for cells with custom render functions */ _syncSlottedContent() { const columns = this.getDisplayedColumns().filter(col => col.render); if (!columns.length) return; // Clear old slotted content this.querySelectorAll('[slot^=\"cell-\"]').forEach(el => el.remove()); // Create new slotted content this._data.forEach((row, rowIndex) => { columns.forEach(col => { const result = col.render(row); if (!result) return; const el = document.createElement('span'); el.slot = `cell-${rowIndex}-${col.id}`; if (col.type === 'actions') { el.style.display = 'flex'; el.style.gap = '8px'; } if (typeof result === 'string') { el.innerHTML = result; } else { D(result, el); } this.appendChild(el); }); }); } // ---------------------------------------------------------------------------- // Public Interface // ---------------------------------------------------------------------------- refresh() { this._fetch(); } goToPrevPage() { if (this._page > 1) { this._page--; this._fetch(); } } goToNextPage() { if (this._page < this._totalPages) { this._page++; this._fetch(); } } goToPage(page) { if (page >= 1 && page <= this._totalPages) { this._page = page; this._fetch(); } } // ---------------------------------------------------------------------------- // Data Fetching // ---------------------------------------------------------------------------- _toSolrData() { const request = { page: this._page - 1, size: this._pageSize, sorts: [], filterFields: [], queryFields: [], facetFields: [] }; for (const col of this._model.columns) { if (!col.filter || col.filter.isEmpty() || !col.filter.isValid()) { continue; } const filterData = col.filter.toSolrData(); if (col.facetable && col.filter.operator === 'in') { filterData.tagged = true; } request.filterFields.push(filterData); } for (const col of this._model.columns) { if (!col.facetable) { continue; } request.facetFields.push({ name: col.id, type: 'FIELD', limit: 100, sort: 'count', minimumCount: 1 }); } if (this._searchQuery?.trim().length) { request.queryFields.push({ name: '_text_', operation: 'IS', value: termify(this._searchQuery, false) }); } return request; } _toDbParams() { const request = { page: this._page - 1, size: this._pageSize, sorts: [], filterFields: [], queryFields: [], facetFields: [] }; for (const col of this._model.columns) { if (!col.filter || col.filter.isEmpty() || !col.filter.isValid()) { continue; } request.filterFields.push(col.filter.toDbParams()); } if (this._searchQuery?.trim().length) { this._model.columns.filter(col => col.searchable).forEach(col => { request.queryFields.push({ name: col.id, operation: 'CONTAINS', value: this._searchQuery, and: false }); }); } return request; } /** * Fetches data from the API and updates the table. * Shows a loading spinner while fetching, then displays rows on success * or an error snackbar on failure. * Request/response format depends on dataSource.mode (solr, opensearch, db). */ _fetch() { if (this._model.data) { this._data = this._model.data; this._totalItems = this._model.data.length; this._totalPages = Math.ceil(this._model.data.length / this._pageSize); this._dataState = 'success'; return; } if (!this._model.dataSource) return; this._dataState = 'loading'; let request; if (this._model.dataSource.mode === 'db') { request = this._toDbParams(); } else { request = this._toSolrData(); } this._model.dataSource.fetch(request) .then(response => { // Parse response based on mode switch (this._model.dataSource?.mode) { case 'opensearch': { throw Error('Opensearch not supported yet'); } case 'db': { const res = response; this._data = res.data.content; this._totalItems = res.data.totalElements; this._totalPages = res.data.totalPages; this._pageSize = res.data.size; break; } default: { // solr const res = response; this._data = res.data.content; this._totalItems = res.data.totalElements; this._totalPages = res.data.totalPages; this._pageSize = res.data.size; this._parseFacetResults(res); } } this._dataState = 'success'; this._updateSearchPosition(); }) .catch(err => { this._dataState = 'error'; KRSnackbar.show({ message: err instanceof Error ? err.message : 'Failed to load data', type: 'error' }); }); } _parseFacetResults(response) { if (!response.data.facetFields) { return; } for (const col of this._model.columns) { if (!col.facetable) { continue; } const rawBuckets = response.data.facetFields[col.id]; if (!rawBuckets) { this._buckets.set(col.id, []); continue; } const buckets = []; for (const raw of rawBuckets) { // Solr returns boolean facet values as strings — coerce to actual booleans // so they match the filter values stored by toggle(). let val = raw.name; if (col.type === 'boolean' && typeof raw.name === 'string') { if (raw.name === 'true') { val = true; } else if (raw.name === 'false') { val = false; } } if (raw.name === null && raw.count > 0) { buckets.unshift({ val: null, count: raw.count }); } if (raw.name !== null) { buckets.push({ val: val, count: raw.count }); } } // Bucket sync: ensure selected values appear even with 0 results if (col.filter && col.filter.operator === 'in' && Array.isArray(col.filter.value)) { for (const selectedVal of col.filter.value) { if (!buckets.some(b => b.val === selectedVal)) { buckets.push({ val: selectedVal, count: 0 }); } } } this._buckets.set(col.id, buckets); } // Trigger re-render since Map mutation doesn't trigger Lit updates this._buckets = new Map(this._buckets); } /** * Sets up auto-refresh so the table automatically fetches fresh data * at a regular interval (useful for dashboards, monitoring views). * Configured via def.refreshInterval in milliseconds. */ _initRefresh() { clearInterval(this._refreshTimer); if (this._model.refreshInterval && this._model.refreshInterval > 0) { this._refreshTimer = window.setInterval(() => { this._fetch(); }, this._model.refreshInterval); } } _handleSearch(e) { const input = e.target; this._searchQuery = input.value; this._page = 1; this._fetch(); } _getGridTemplateColumns() { const cols = this.getDisplayedColumns(); return cols.map((col) => { // If column has explicit width, use it if (col.width) { return col.width; } // Actions columns: fit content without minimum if (col.type === 'actions') { return 'max-content'; } // No width specified - use content-based sizing with minimum return 'minmax(80px, auto)'; }).join(' '); } /** * Updates search position to be centered with equal gaps from title and tools. * On first call: resets to flex centering, measures position, then locks with fixed margin. * Subsequent calls are ignored unless _searchPositionLocked is reset (e.g., on resize). */ _updateSearchPosition() { // Skip if already locked (prevents shifts on pagination changes) if (this._searchPositionLocked) return; const search = this.shadowRoot?.querySelector('.search'); const searchField = search?.querySelector('.search-field'); if (!search || !searchField) return; // Reset to flex centering search.style.justifyContent = 'center'; searchField.style.marginLeft = ''; requestAnimationFrame(() => { const searchRect = search.getBoundingClientRect(); const fieldRect = searchField.getBoundingClientRect(); // Calculate how far from the left of search container the field currently is const currentOffset = fieldRect.left - searchRect.left; // Lock position: switch to flex-start and use fixed margin search.style.justifyContent = 'flex-start'; searchField.style.marginLeft = `${currentOffset}px`; // Mark as locked so pagination changes don't shift the search this._searchPositionLocked = true; }); } // ---------------------------------------------------------------------------- // Columns // ---------------------------------------------------------------------------- _toggleColumnPicker() { this._columnPickerOpen = !this._columnPickerOpen; } _toggleColumn(columnId) { if (this._model.displayedColumns.includes(columnId)) { this._model.displayedColumns = this._model.displayedColumns.filter(id => id !== columnId); } else { this._model.displayedColumns = [...this._model.displayedColumns, columnId]; } } // Clear any existing text selection on mousedown so we only detect // selections made during this click gesture, not stale selections from elsewhere _handleRowMouseDown() { if (!this._model.rowClickable) { return; } window.getSelection()?.removeAllRanges(); } _handleRowClick(row, rowIndex) { if (!this._model.rowClickable) { return; } const selection = window.getSelection(); if (selection && selection.toString().length > 0) { return; } this.dispatchEvent(new CustomEvent('row-click', { detail: { row, rowIndex }, bubbles: true, composed: true })); } // When a user toggles a column on via the column picker, it gets appended // to _displayedColumns. By mapping over _displayedColumns (not def.columns), // the new column appears at the right edge of the table instead of jumping // back to its original position in the column definition. // Actions columns are always moved to the end. getDisplayedColumns() { return this._model.displayedColumns .map(id => this._model.columns.find(col => col.id === id)) .sort((a, b) => { if (a.type === 'actions' && b.type !== 'actions') return 1; if (a.type !== 'actions' && b.type === 'actions') return -1; return 0; }); } // ---------------------------------------------------------------------------- // Scrolling // ---------------------------------------------------------------------------- /** * Scroll event handler that updates scroll flags in real-time as user scrolls. * Updates shadow indicators to show if more content exists left/right. */ _handleScroll(e) { const container = e.target; this._canScrollLeft = container.scrollLeft > 0; this._canScrollRight = container.scrollLeft < container.scrollWidth - container.clientWidth - 1; } /** * Updates scroll state flags for the table content container. * - _canScrollLeft: true if scrolled right (can scroll back left) * - _canScrollRight: true if more content exists to the right * - _canScrollHorizontal: true if content is wider than container * These flags control scroll shadow indicators and CSS classes. */ _updateScrollFlags() { const container = this.shadowRoot?.querySelector('.content'); if (container) { this._canScrollLeft = container.scrollLeft > 0; this._canScrollRight = container.scrollWidth > container.clientWidth && container.scrollLeft < container.scrollWidth - container.clientWidth - 1; this._canScrollHorizontal = container.scrollWidth > container.clientWidth; } this.classList.toggle('kr-table--scroll-left-available', this._canScrollLeft); this.classList.toggle('kr-table--scroll-right-available', this._canScrollRight); this.classList.toggle('kr-table--scroll-horizontal-available', this._canScrollHorizontal); this.classList.toggle('kr-table--sticky-left', this.getDisplayedColumns().some(c => c.sticky === 'left')); this.classList.toggle('kr-table--sticky-right', this.getDisplayedColumns().some(c => c.sticky === 'right')); } // ---------------------------------------------------------------------------- // Column Resizing // ---------------------------------------------------------------------------- _handleResizeStart(e, columnId) { e.preventDefault(); const headerCell = this.shadowRoot?.querySelector(`.header-cell[data-column-id=\"${columnId}\"]`); this._resizing = { columnId, startX: e.clientX, startWidth: headerCell?.offsetWidth || 200 }; document.addEventListener('mousemove', this._handleResizeMove); document.addEventListener('mouseup', this._handleResizeEnd); } // ---------------------------------------------------------------------------- // Header // ---------------------------------------------------------------------------- _handleAction(action) { if (action.href) { return; } this.dispatchEvent(new CustomEvent('action', { detail: { action: action.id }, bubbles: true, composed: true })); } // ---------------------------------------------------------------------------- // Filter Handlers // ---------------------------------------------------------------------------- _handleKqlChange(e, column) { const kql = e.target.value.trim(); if (!kql) { column.filter.clear(); this.requestUpdate(); } else { column.filter.setKql(kql); this.requestUpdate(); if (!column.filter.isValid()) { return; } } this._page = 1; this._fetch(); } _handleFilterPanelToggle(e, column) { e.stopPropagation(); if (this._filterPanelOpened === column.id) { this._filterPanelOpened = null; } else { const rect = e.currentTarget.getBoundingClientRect(); this._filterPanelPos = { top: rect.bottom + 4, left: rect.left }; this._filterPanelOpened = column.id; if (column.facetable) { this._filterPanelTab = 'counts'; } else { this._filterPanelTab = 'filter'; } } } _handleKqlClear(column) { column.filter.clear(); this._page = 1; this._fetch(); } _handleFilterClear() { const column = this._model.columns.find(c => c.id === this._filterPanelOpened); if (column) { column.filter.clear(); if (column.facetable && !column.filterable) { column.filter.operator = 'in'; column.filter.value = []; } } this._filterPanelOpened = null; this._page = 1; this._fetch(); } _handleFilterTextKeydown(e, column) { if (e.key === 'Enter') { e.preventDefault(); this._handleFilterApply(); } } _handleOperatorChange(e, column) { column.filter.setOperator(e.target.value); this.requestUpdate(); } _handleFilterStringChange(e, column) { column.filter.setValue(e.target.value); this.requestUpdate(); } _handleFilterNumberChange(e, column) { column.filter.setValue(Number(e.target.value)); this.requestUpdate(); } _handleFilterDateChange(e, column) { column.filter.setValue(new Date(e.target.value), 'day'); this.requestUpdate(); } _handleFilterBooleanChange(e, column) { column.filter.setValue(e.target.value === 'true'); this.requestUpdate(); } _handleFilterDateStartChange(e, column) { column.filter.setStart(new Date(e.target.value), 'day'); this.requestUpdate(); } _handleFilterDateEndChange(e, column) { column.filter.setEnd(new Date(e.target.value), 'day'); this.requestUpdate(); } _handleFilterNumberStartChange(e, column) { column.filter.setStart(Number(e.target.value)); this.requestUpdate(); } _handleFilterNumberEndChange(e, column) { column.filter.setEnd(Number(e.target.value)); this.requestUpdate(); } _handleFilterListChange(e, column) { const items = e.target.value.split(',').map((v) => v.trim()).filter((v) => v !== ''); if (column.type === 'number') { column.filter.setValue(items.map((v) => Number(v))); } else { column.filter.setValue(items); } this.requestUpdate(); } _handleFilterApply() { this._filterPanelOpened = null; this._page = 1; this._fetch(); } _handleFilterPanelTabChange(e) { this._filterPanelTab = e.detail.activeTabId; } _handleBucketToggle(e, column, bucket) { column.filter.toggle(bucket.val); this._page = 1; this._fetch(); } // ---------------------------------------------------------------------------- // Rendering // ---------------------------------------------------------------------------- _renderCellContent(column, row, rowIndex) { const value = row[column.id]; if (column.render) { // Use slot to project content from light DOM so external styles apply return b `<slot name=\"cell-${rowIndex}-${column.id}\"></slot>`; } if (value === null || value === undefined) { return ''; } switch (column.type) { case 'number': if (column.format === 'currency' && typeof value === 'number') { return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' }); } return String(value); case 'date': { let date; if (value instanceof Date) { date = value; } else if (typeof value === 'string' && /^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}/.test(value)) { // MySQL datetime format (UTC): \"2026-01-28 01:33:44:517\" // Replace last colon before ms with dot, append Z for UTC const isoString = value.replace(/(\\d{2}:\\d{2}:\\d{2}):(\\d+)$/, '$1.$2').replace(' ', 'T') + 'Z'; date = new Date(isoString); } else { date = new Date(value); } // Show date and time for datetime values in UTC return date.toLocaleString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', timeZone: 'UTC' }); } case 'boolean': if (value === true) return 'Yes'; if (value === false) return 'No'; return ''; default: return String(value); } } /** * Returns CSS classes for a header cell based on column config. */ _getHeaderCellClasses(column, index) { return { 'header-cell': true, 'header-cell--align-center': column.align === 'center', 'header-cell--align-right': column.align === 'right', 'header-cell--sticky-left': column.sticky === 'left', 'header-cell--sticky-left-last': column.sticky === 'left' && !this.getDisplayedColumns().slice(index + 1).some(c => c.sticky === 'left'), 'header-cell--sticky-right': column.sticky === 'right', 'header-cell--sticky-right-first': column.sticky === 'right' && !this.getDisplayedColumns().slice(0, index).some(c => c.sticky === 'right') }; } /** * Returns CSS classes for a table cell based on column config: * - Alignment (center, right) * - Sticky positioning (left, right) * - Border classes for the last left-sticky or first right-sticky column */ _getCellClasses(column, index) { return { 'cell': true, 'cell--actions': column.type === 'actions', 'cell--align-center': column.align === 'center', 'cell--align-right': column.align === 'right', 'cell--sticky-left': column.sticky === 'left', 'cell--sticky-left-last': column.sticky === 'left' && !this.getDisplayedColumns().slice(index + 1).some(c => c.sticky === 'left'), 'cell--sticky-right': column.sticky === 'right', 'cell--sticky-right-first': column.sticky === 'right' && !this.getDisplayedColumns().slice(0, index).some(c => c.sticky === 'right') }; } /** * Returns inline styles for a table cell: * - Width (from column config or default 150px) * - Min-width (if specified) * - Left/right offset for sticky columns (calculated from widths of preceding sticky columns) */ _getCellStyle(column, index) { const styles = {}; if (column.sticky === 'left') { let leftOffset = 0; for (let i = 0; i < index; i++) { const col = this.getDisplayedColumns()[i]; if (col.sticky === 'left') { leftOffset += parseInt(col.width || '0', 10); } } styles.left = `${leftOffset}px`; } if (column.sticky === 'right') { let rightOffset = 0; for (let i = index + 1; i < this.getDisplayedColumns().length; i++) { const col = this.getDisplayedColumns()[i]; if (col.sticky === 'right') { rightOffset += parseInt(col.width || '0', 10); } } styles.right = `${rightOffset}px`; } return styles; } /** * Renders the pagination controls: * - Previous page arrow (disabled on first page) * - Range text showing \"1-50 of 150\" format * - Next page arrow (disabled on last page) * * Hidden when there's no data or all data fits on one page. */ _renderPagination() { const start = (this._page - 1) * this._pageSize + 1; const end = Math.min(this._page * this._pageSize, this._totalItems); return b ` <div class=\"pagination\"> <span class=\"pagination-icon ${this._page === 1 ? 'pagination-icon--disabled' : ''}\" @click=${this.goToPrevPage} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\"/></svg> </span> <span class=\"pagination-info\">${start}-${end} of ${this._totalItems}</span> <span class=\"pagination-icon ${this._page === this._totalPages ? 'pagination-icon--disabled' : ''}\" @click=${this.goToNextPage} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\"/></svg> </span> </div> `; } /** * Renders the header toolbar containing: * - Title (left) * - Search bar with view selector dropdown (center) * - Tools (right): page navigation, refresh button, column visibility picker, actions dropdown * * Hidden when there's no title, no actions, and data fits on one page. */ _renderHeader() { if (!this._model.title && !this._model.actions?.length && this._totalPages <= 1) { return A; } return b ` <div class=\"header\"> <div class=\"title\">${this._model.title ?? ''}</div> ${this._model.dataSource?.mode === 'db' && !this._model.columns.some(col => col.searchable) ? b `<div class=\"search\"></div>` : b ` <div class=\"search\"> <!-- TODO: Saved views dropdown <div class=\"views\"> <span>Default View</span> <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z\"/></svg> </div> --> <div class=\"search-field\"> <svg class=\"search-icon\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z\"/></svg> <input type=\"text\" class=\"search-input\" placeholder=\"Search...\" .value=${this._searchQuery} @input=${this._handleSearch} /> </div> </div> `} <div class=\"tools\"> ${this._renderPagination()} <span class=\"refresh\" title=\"Refresh\" @click=${() => this.refresh()}> <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z\"/></svg> </span> <div class=\"column-picker-wrapper\"> <span class=\"header-icon\" title=\"Columns\" @click=${this._toggleColumnPicker}> <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M121-280v-400q0-33 23.5-56.5T201-760h559q33 0 56.5 23.5T840-680v400q0 33-23.5 56.5T760-200H201q-33 0-56.5-23.5T121-280Zm79 0h133v-400H200v400Zm213 0h133v-400H413v400Zm213 0h133v-400H626v400Z\"/></svg> </span> <div class=\"column-picker ${this._columnPickerOpen ? 'open' : ''}\"> ${[...this._model.columns].filter(col => col.type !== 'actions').sort((a, b) => (a.label ?? a.id).localeCompare(b.label ?? b.id)).map(col => b ` <div class=\"column-picker-item\" @click=${() => this._toggleColumn(col.id)}> <div class=\"column-picker-checkbox ${this._model.displayedColumns.includes(col.id) ? 'checked' : ''}\"> <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/></svg> </div> <span class=\"column-picker-label\">${col.label ?? col.id}</span> </div> `)} </div> </div> ${this._model.actions?.length === 1 ? b ` <kr-button class=\"actions\" .href=${this._model.actions[0].href} .target=${this._model.actions[0].target} @click=${() => this._handleAction(this._model.actions[0])} > ${this._model.actions[0].label} </kr-button> ` : this._model.actions?.length ? b ` <kr-button class=\"actions\" .options=${this._model.actions.map(a => ({ id: a.id, label: a.label }))} @option-select=${(e) => this._handleAction({ id: e.detail.id, label: e.detail.label })} > Actions </kr-button> ` : A} </div> </div> `; } /** Renders status message (loading, error, empty) */ _renderStatus() { if (this._dataState === 'loading' && this._data.length === 0) { return b `<div class=\"status\">Loading...</div>`; } if (this._dataState === 'error' && this._data.length === 0) { return b `<div class=\"status status--error\">Error loading data</div>`; } if (this._data.length === 0) { return b `<div class=\"status\">No data available</div>`; } return A; } _renderFilterPanel() { if (!this._filterPanelOpened) { return A; } const column = this._model.columns.find(c => c.id === this._filterPanelOpened); // Build filter content (operator + value input) let valueInput = b ``; if (column.filter.operator === 'empty' || column.filter.operator === 'n_empty') { valueInput = b ` <input type=\"text\" class=\"filter-panel__input\" disabled .value=${column.filter.text} /> `; } else if (column.filter.operator === 'between' && column.type === 'date') { valueInput = b ` <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${column.filter.value?.start ?? null} @change=${(e) => this._handleFilterDateStartChange(e, column)} /> <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${column.filter.value?.end ?? null} @change=${(e) => this._handleFilterDateEndChange(e, column)} /> `; } else if (column.filter.operator === 'between' && column.type === 'number') { valueInput = b ` <input type=\"number\" class=\"filter-panel__input\" placeholder=\"Start\" .value=${column.filter.value?.start ?? ''} @input=${(e) => this._handleFilterNumberStartChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> <input type=\"number\" class=\"filter-panel__input\" placeholder=\"End\" .value=${column.filter.value?.end ?? ''} @input=${(e) => this._handleFilterNumberEndChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> `; } else if (column.filter.operator === 'in') { valueInput = b ` <textarea class=\"filter-panel__textarea\" rows=\"3\" placeholder=\"Values (comma-separated)\" .value=${column.filter.text} @input=${(e) => this._handleFilterListChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} ></textarea> `; } else if (column.type === 'boolean') { valueInput = b ` <kr-select-field placeholder=\"Value\" .value=${String(column.filter.value ?? '')} @change=${(e) => this._handleFilterBooleanChange(e, column)} > <kr-select-option value=\"true\">Yes</kr-select-option> <kr-select-option value=\"false\">No</kr-select-option> </kr-select-field> `; } else if (column.type === 'date') { valueInput = b ` <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${column.filter.value} @change=${(e) => this._handleFilterDateChange(e, column)} /> `; } else if (column.type === 'number') { valueInput = b ` <input type=\"number\" class=\"filter-panel__input\" placeholder=\"Value\" min=\"0\" .value=${column.filter.text} @input=${(e) => this._handleFilterNumberChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> `; } else { valueInput = b ` <input type=\"text\" class=\"filter-panel__input\" placeholder=\"Value\" .value=${column.filter.text} @input=${(e) => this._handleFilterStringChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> `; } const filterContent = b ` <div class=\"filter-panel__content\"> <kr-select-field .value=${column.filter.operator} @change=${(e) => this._handleOperatorChange(e, column)} > ${getOperatorsForType(column.type).map(op => b ` <kr-select-option value=${op.key}>${op.label}</kr-select-option> `)} </kr-select-field> ${valueInput} </div> `; // Build bucket list content const buckets = this._buckets.get(column.id) || []; let bucketContent; if (!buckets.length) { bucketContent = b `<div class=\"bucket-empty\">No data</div>`; } else { bucketContent = b ` <div class=\"buckets\"> ${buckets.map(bucket => { let bucketLabel = '(Empty)'; if (bucket.val !== null && bucket.val !== undefined) { if (column.type === 'boolean') { if (bucket.val === true || bucket.val === 'true') { bucketLabel = 'Yes'; } else { bucketLabel = 'No'; } } else { bucketLabel = String(bucket.val); } } let checkIcon = A; if (column.filter.has(bucket.val)) { checkIcon = b ` <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/> </svg> `; } return b ` <div class=\"bucket\" @click=${(e) => this._handleBucketToggle(e, column, bucket)} > <div class=${e$1({ 'bucket__checkbox': true, 'bucket__checkbox--checked': column.filter.has(bucket.val) })}> ${checkIcon} </div> <span class=\"bucket__label\">${bucketLabel}</span> <span class=\"bucket__count\">${bucket.count}</span> </div> `; })} </div> `; } // Build panel body — tabs if both filterable+facetable, otherwise just the relevant content let panelBody; if (column.facetable && column.filterable) { panelBody = b ` <kr-tab-group size=\"small\" active-tab-id=${this._filterPanelTab} @tab-change=${(e) => this._handleFilterPanelTabChange(e)} > <kr-tab id=\"filter\" label=\"Filter\"> ${filterContent} </kr-tab> <kr-tab id=\"counts\" label=\"Counts\"> ${bucketContent} </kr-tab> </kr-tab-group> `; } else if (column.facetable) { panelBody = bucketContent; } else { panelBody = filterContent; } return b ` <div class=\"filter-panel\" style=${o$1({ top: this._filterPanelPos.top + 'px', left: this._filterPanelPos.left + 'px' })} > ${panelBody} <div class=\"filter-panel__actions\"> <kr-button variant=\"outline\" color=\"secondary\" size=\"small\" @click=${this._handleFilterClear}> Clear </kr-button> <kr-button size=\"small\" @click=${this._handleFilterApply}> Apply </kr-button> </div> </div> `; } /** * Renders filter row below column headers. * Only displays for columns with filterable: true. */ _renderFilterRow() { const columns = this.getDisplayedColumns(); if (!columns.some(col => col.filterable || col.facetable)) { return A; } return b ` <div class=\"filter-row\"> ${columns.map((col, i) => { if (!col.filterable && !col.facetable) { return b `<div class=${e$1({ 'filter-cell': true, 'filter-cell--sticky-left': col.sticky === 'left', 'filter-cell--sticky-right': col.sticky === 'right', 'filter-cell--sticky-right-first': col.sticky === 'right' && !columns.slice(0, i).some((c) => c.sticky === 'right') })} style=${o$1(this._getCellStyle(col, i))} ></div>`; } return b ` <div class=${e$1({ 'filter-cell': true, 'filter-cell--sticky-left': col.sticky === 'left', 'filter-cell--sticky-right': col.sticky === 'right', 'filter-cell--sticky-right-first': col.sticky === 'right' && !columns.slice(0, i).some((c) => c.sticky === 'right') })} style=${o$1(this._getCellStyle(col, i))} > <div class=\"filter-cell__wrapper\"> <input type=\"text\" class=${e$1({ 'filter-cell__input': true, 'filter-cell__input--invalid': !col.filter.isValid() })} .value=${col.filter.kql} @change=${(e) => this._handleKqlChange(e, col)} /> ${col.filter?.kql?.length > 0 ? b ` <button class=\"filter-cell__clear\" @click=${() => this._handleKqlClear(col)} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/> </svg> </button> ` : A} <button class=${e$1({ 'filter-cell__advanced': true, 'filter-cell__advanced--opened': this._filterPanelOpened === col.id })} @click=${(e) => this._handleFilterPanelToggle(e, col)} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z\"/> </svg> </button> </div> </div> `; })} </div> `; } /** Renders the scrollable data grid with column headers and rows. */ _renderTable() { return b ` <div class=\"wrapper\"> <div class=\"overlay-left\"></div> <div class=\"overlay-right\"></div> ${this._renderStatus()} <div class=\"content\" @scroll=${this._handleScroll}> <div class=\"table\" style=\"grid-template-columns: ${this._getGridTemplateColumns()}\"> <div class=\"header-row\"> ${this.getDisplayedColumns().map((col, i) => b ` <div class=${e$1(this._getHeaderCellClasses(col, i))} style=${o$1(this._getCellStyle(col, i))} data-column-id=${col.id} >${col.label ?? col.id}${col.resizable !== false ? b `<div class=\"header-cell__resize\" @mousedown=${(e) => this._handleResizeStart(e, col.id)} ></div>` : A}</div> `)} </div> ${this._renderFilterRow()} ${this._data.map((row, rowIndex) => { const cells = this.getDisplayedColumns().map((col, i) => b ` <div class=${e$1(this._getCellClasses(col, i))} style=${o$1(this._getCellStyle(col, i))} data-column-id=${col.id} > ${this._renderCellContent(col, row, rowIndex)} </div> `); if (this._model.rowHref) { return b ` <a href=${this._model.rowHref(row)} class=${e$1({ 'row': true, 'row--clickable': true, 'row--link': true })} @mousedown=${() => this._handleRowMouseDown()} @click=${() => this._handleRowClick(row, rowIndex)} >${cells}</a> `; } return b ` <div class=${e$1({ 'row': true, 'row--clickable': !!this._model.rowClickable })} @mousedown=${() => this._handleRowMouseDown()} @click=${() => this._handleRowClick(row, rowIndex)} >${cells}</div> `; })} </div> </div> </div> `; } /** * Renders a data table with: * - Header bar with title, search input with view selector, and tools (pagination, refresh, column visibility, actions dropdown) * - Scrollable grid with sticky header row and optional sticky left/right columns * - Loading, error message, or empty state when no data */ render() { if (!this._model.columns.length) { return b `<slot></slot>`; } return b ` ${this._renderHeader()} ${this._renderTable()} ${this._renderFilterPanel()} `; } }"
576
+ "default": "class KRTable extends i$2 { constructor() { super(...arguments); /** * Internal flag to switch between scroll edge modes: * - 'overlay': Fixed padding with overlay elements that hide content at edges (scrollbar at viewport edge) * - 'edge': Padding scrolls with content, allowing table to reach edges when scrolling */ this._scrollStyle = 'overlay'; this._data = []; this._dataState = 'idle'; this._page = 1; this._pageSize = 50; this._totalItems = 0; this._totalPages = 0; this._searchQuery = ''; this._canScrollLeft = false; this._canScrollRight = false; this._canScrollHorizontal = false; this._columnPickerOpen = false; this._filterPanelOpened = null; this._filterPanelTab = 'filter'; this._buckets = new Map(); this._filterPanelPos = { top: 0, left: 0 }; this._resizing = null; this._resizeObserver = null; this._searchPositionLocked = false; this._model = new KRTableModel(); this.def = { columns: [] }; this._handleClickOutside = (e) => { const path = e.composedPath(); if (this._columnPickerOpen) { const picker = this.shadowRoot?.querySelector('.column-picker-wrapper'); if (picker && !path.includes(picker)) { this._columnPickerOpen = false; } } if (this._filterPanelOpened) { if (!path.some((el) => el.classList?.contains('filter-panel'))) { this._handleFilterApply(); } } }; this._handleResizeMove = (e) => { if (!this._resizing) return; const col = this._model.columns.find(c => c.id === this._resizing.columnId); if (col) { const newWidth = this._resizing.startWidth + (e.clientX - this._resizing.startX); col.width = `${Math.min(900, Math.max(50, newWidth))}px`; this.requestUpdate(); } }; this._handleResizeEnd = () => { this._resizing = null; document.removeEventListener('mousemove', this._handleResizeMove); document.removeEventListener('mouseup', this._handleResizeEnd); }; } connectedCallback() { super.connectedCallback(); this.classList.toggle('kr-table--scroll-overlay', this._scrollStyle === 'overlay'); this.classList.toggle('kr-table--scroll-edge', this._scrollStyle === 'edge'); this._fetch(); this._initRefresh(); document.addEventListener('click', this._handleClickOutside); this._resizeObserver = new ResizeObserver(() => { // Unlock and recalculate on resize since layout changes this._searchPositionLocked = false; this._updateSearchPosition(); }); this._resizeObserver.observe(this); } disconnectedCallback() { super.disconnectedCallback(); clearInterval(this._refreshTimer); document.removeEventListener('click', this._handleClickOutside); this._resizeObserver?.disconnect(); } willUpdate(changedProperties) { if (changedProperties.has('def')) { // Build internal model from user-provided def this._model = new KRTableModel(); if (this.def.title) { this._model.title = this.def.title; } if (this.def.actions) { this._model.actions = this.def.actions; } if (this.def.data) { this._model.data = this.def.data; } if (this.def.dataSource) { this._model.dataSource = this.def.dataSource; } if (typeof this.def.refreshInterval === 'number') { this._model.refreshInterval = this.def.refreshInterval; } if (typeof this.def.pageSize === 'number') { this._model.pageSize = this.def.pageSize; } if (this.def.rowClickable) { this._model.rowClickable = this.def.rowClickable; } if (this.def.rowHref) { this._model.rowHref = this.def.rowHref; } this._model.columns = this.def.columns.map(col => { const column = { ...col, filter: null }; if (!column.type) { column.type = 'string'; } if (column.type === 'actions') { column.label = col.label ?? ''; column.sticky = 'right'; column.resizable = false; return column; } if (col.filterable || col.facetable) { column.filter = new KRQuery(); column.filter.field = col.id; column.filter.type = column.type; if (col.facetable && !col.filterable) { column.filter.operator = 'in'; column.filter.value = []; } else if (column.filter.type === 'string') { column.filter.operator = 'contains'; } } return column; }); if (this.def.displayedColumns) { this._model.displayedColumns = this.def.displayedColumns; } else { this._model.displayedColumns = this._model.columns.map(c => c.id); } this._fetch(); this._initRefresh(); } } updated(changedProperties) { this._updateScrollFlags(); this._syncSlottedContent(); } /** Syncs light DOM content for cells with custom render functions */ _syncSlottedContent() { const columns = this.getDisplayedColumns().filter(col => col.render); if (!columns.length) return; // Clear old slotted content this.querySelectorAll('[slot^=\"cell-\"]').forEach(el => el.remove()); // Create new slotted content this._data.forEach((row, rowIndex) => { columns.forEach(col => { const result = col.render(row); if (!result) return; const el = document.createElement('span'); el.slot = `cell-${rowIndex}-${col.id}`; if (col.type === 'actions') { el.style.display = 'flex'; el.style.gap = '8px'; } if (typeof result === 'string') { el.innerHTML = result; } else { D(result, el); } this.appendChild(el); }); }); } // ---------------------------------------------------------------------------- // Public Interface // ---------------------------------------------------------------------------- refresh() { this._fetch(); } goToPrevPage() { if (this._page > 1) { this._page--; this._fetch(); } } goToNextPage() { if (this._page < this._totalPages) { this._page++; this._fetch(); } } goToPage(page) { if (page >= 1 && page <= this._totalPages) { this._page = page; this._fetch(); } } // ---------------------------------------------------------------------------- // Data Fetching // ---------------------------------------------------------------------------- _toSolrData() { const request = { page: this._page - 1, size: this._pageSize, sorts: [], filterFields: [], queryFields: [], facetFields: [] }; for (const col of this._model.columns) { if (!col.filter || col.filter.isEmpty() || !col.filter.isValid()) { continue; } const filterData = col.filter.toSolrData(); if (col.facetable && col.filter.operator === 'in') { filterData.tagged = true; } request.filterFields.push(filterData); } for (const col of this._model.columns) { if (!col.facetable) { continue; } request.facetFields.push({ name: col.id, type: 'FIELD', limit: 100, sort: 'count', minimumCount: 1 }); } if (this._searchQuery?.trim().length) { request.queryFields.push({ name: '_text_', operation: 'IS', value: termify(this._searchQuery, false) }); } return request; } _toDbParams() { const request = { page: this._page - 1, size: this._pageSize, sorts: [], filterFields: [], queryFields: [], facetFields: [] }; for (const col of this._model.columns) { if (!col.filter || col.filter.isEmpty() || !col.filter.isValid()) { continue; } request.filterFields.push(col.filter.toDbParams()); } if (this._searchQuery?.trim().length) { this._model.columns.filter(col => col.searchable).forEach(col => { request.queryFields.push({ name: col.id, operation: 'CONTAINS', value: this._searchQuery, and: false }); }); } return request; } /** * Fetches data from the API and updates the table. * Shows a loading spinner while fetching, then displays rows on success * or an error snackbar on failure. * Request/response format depends on dataSource.mode (solr, opensearch, db). */ _fetch() { if (this._model.data) { this._data = this._model.data; this._totalItems = this._model.data.length; this._totalPages = Math.ceil(this._model.data.length / this._pageSize); this._dataState = 'success'; return; } if (!this._model.dataSource) return; this._dataState = 'loading'; let request; if (this._model.dataSource.mode === 'db') { request = this._toDbParams(); } else { request = this._toSolrData(); } this._model.dataSource.fetch(request) .then(response => { // Parse response based on mode switch (this._model.dataSource?.mode) { case 'opensearch': { throw Error('Opensearch not supported yet'); } case 'db': { const res = response; this._data = res.data.content; this._totalItems = res.data.totalElements; this._totalPages = res.data.totalPages; this._pageSize = res.data.size; break; } default: { // solr const res = response; this._data = res.data.content; this._totalItems = res.data.totalElements; this._totalPages = res.data.totalPages; this._pageSize = res.data.size; this._parseFacetResults(res); } } this._dataState = 'success'; this._updateSearchPosition(); }) .catch(err => { this._dataState = 'error'; KRSnackbar.show({ message: err instanceof Error ? err.message : 'Failed to load data', type: 'error' }); }); } _parseFacetResults(response) { if (!response.data.facetFields) { return; } for (const col of this._model.columns) { if (!col.facetable) { continue; } const rawBuckets = response.data.facetFields[col.id]; if (!rawBuckets) { this._buckets.set(col.id, []); continue; } const buckets = []; for (const raw of rawBuckets) { // Solr returns boolean facet values as strings — coerce to actual booleans // so they match the filter values stored by toggle(). let val = raw.name; if (col.type === 'boolean' && typeof raw.name === 'string') { if (raw.name === 'true') { val = true; } else if (raw.name === 'false') { val = false; } } if (raw.name === null && raw.count > 0) { buckets.unshift({ val: null, count: raw.count }); } if (raw.name !== null) { buckets.push({ val: val, count: raw.count }); } } // Bucket sync: ensure selected values appear even with 0 results if (col.filter && col.filter.operator === 'in' && Array.isArray(col.filter.value)) { for (const selectedVal of col.filter.value) { if (!buckets.some(b => b.val === selectedVal)) { buckets.push({ val: selectedVal, count: 0 }); } } } this._buckets.set(col.id, buckets); } // Trigger re-render since Map mutation doesn't trigger Lit updates this._buckets = new Map(this._buckets); } /** * Sets up auto-refresh so the table automatically fetches fresh data * at a regular interval (useful for dashboards, monitoring views). * Configured via def.refreshInterval in milliseconds. */ _initRefresh() { clearInterval(this._refreshTimer); if (this._model.refreshInterval && this._model.refreshInterval > 0) { this._refreshTimer = window.setInterval(() => { this._fetch(); }, this._model.refreshInterval); } } _handleSearch(e) { const input = e.target; this._searchQuery = input.value; this._page = 1; this._fetch(); } _getGridTemplateColumns() { const cols = this.getDisplayedColumns(); return cols.map((col) => { // If column has explicit width, use it if (col.width) { return col.width; } // Actions columns: fit content without minimum if (col.type === 'actions') { return 'max-content'; } // No width specified - use content-based sizing with minimum return 'minmax(80px, auto)'; }).join(' '); } /** * Updates search position to be centered with equal gaps from title and tools. * On first call: resets to flex centering, measures position, then locks with fixed margin. * Subsequent calls are ignored unless _searchPositionLocked is reset (e.g., on resize). */ _updateSearchPosition() { // Skip if already locked (prevents shifts on pagination changes) if (this._searchPositionLocked) return; const search = this.shadowRoot?.querySelector('.search'); const searchField = search?.querySelector('.search-field'); if (!search || !searchField) return; // Reset to flex centering search.style.justifyContent = 'center'; searchField.style.marginLeft = ''; requestAnimationFrame(() => { const searchRect = search.getBoundingClientRect(); const fieldRect = searchField.getBoundingClientRect(); // Calculate how far from the left of search container the field currently is const currentOffset = fieldRect.left - searchRect.left; // Lock position: switch to flex-start and use fixed margin search.style.justifyContent = 'flex-start'; searchField.style.marginLeft = `${currentOffset}px`; // Mark as locked so pagination changes don't shift the search this._searchPositionLocked = true; }); } // ---------------------------------------------------------------------------- // Columns // ---------------------------------------------------------------------------- _toggleColumnPicker() { this._columnPickerOpen = !this._columnPickerOpen; } _toggleColumn(columnId) { if (this._model.displayedColumns.includes(columnId)) { this._model.displayedColumns = this._model.displayedColumns.filter(id => id !== columnId); } else { this._model.displayedColumns = [...this._model.displayedColumns, columnId]; } } // Clear any existing text selection on mousedown so we only detect // selections made during this click gesture, not stale selections from elsewhere _handleRowMouseDown() { if (!this._model.rowClickable) { return; } window.getSelection()?.removeAllRanges(); } _handleRowClick(row, rowIndex) { if (!this._model.rowClickable) { return; } const selection = window.getSelection(); if (selection && selection.toString().length > 0) { return; } this.dispatchEvent(new CustomEvent('row-click', { detail: { row, rowIndex }, bubbles: true, composed: true })); } // When a user toggles a column on via the column picker, it gets appended // to _displayedColumns. By mapping over _displayedColumns (not def.columns), // the new column appears at the right edge of the table instead of jumping // back to its original position in the column definition. // Actions columns are always moved to the end. getDisplayedColumns() { return this._model.displayedColumns .map(id => this._model.columns.find(col => col.id === id)) .sort((a, b) => { if (a.type === 'actions' && b.type !== 'actions') return 1; if (a.type !== 'actions' && b.type === 'actions') return -1; return 0; }); } // ---------------------------------------------------------------------------- // Scrolling // ---------------------------------------------------------------------------- /** * Scroll event handler that updates scroll flags in real-time as user scrolls. * Updates shadow indicators to show if more content exists left/right. */ _handleScroll(e) { const container = e.target; this._canScrollLeft = container.scrollLeft > 0; this._canScrollRight = container.scrollLeft < container.scrollWidth - container.clientWidth - 1; } /** * Updates scroll state flags for the table content container. * - _canScrollLeft: true if scrolled right (can scroll back left) * - _canScrollRight: true if more content exists to the right * - _canScrollHorizontal: true if content is wider than container * These flags control scroll shadow indicators and CSS classes. */ _updateScrollFlags() { const container = this.shadowRoot?.querySelector('.content'); if (container) { this._canScrollLeft = container.scrollLeft > 0; this._canScrollRight = container.scrollWidth > container.clientWidth && container.scrollLeft < container.scrollWidth - container.clientWidth - 1; this._canScrollHorizontal = container.scrollWidth > container.clientWidth; } this.classList.toggle('kr-table--scroll-left-available', this._canScrollLeft); this.classList.toggle('kr-table--scroll-right-available', this._canScrollRight); this.classList.toggle('kr-table--scroll-horizontal-available', this._canScrollHorizontal); this.classList.toggle('kr-table--sticky-left', this.getDisplayedColumns().some(c => c.sticky === 'left')); this.classList.toggle('kr-table--sticky-right', this.getDisplayedColumns().some(c => c.sticky === 'right')); } // ---------------------------------------------------------------------------- // Column Resizing // ---------------------------------------------------------------------------- _handleResizeStart(e, columnId) { e.preventDefault(); const headerCell = this.shadowRoot?.querySelector(`.header-cell[data-column-id=\"${columnId}\"]`); this._resizing = { columnId, startX: e.clientX, startWidth: headerCell?.offsetWidth || 200 }; document.addEventListener('mousemove', this._handleResizeMove); document.addEventListener('mouseup', this._handleResizeEnd); } // ---------------------------------------------------------------------------- // Header // ---------------------------------------------------------------------------- _handleAction(action) { if (action.href) { return; } this.dispatchEvent(new CustomEvent('action', { detail: { action: action.id }, bubbles: true, composed: true })); } // ---------------------------------------------------------------------------- // Filter Handlers // ---------------------------------------------------------------------------- _handleKqlChange(e, column) { const kql = e.target.value.trim(); if (!kql) { column.filter.clear(); this.requestUpdate(); } else { column.filter.setKql(kql); this.requestUpdate(); if (!column.filter.isValid()) { return; } } this._page = 1; this._fetch(); } _handleFilterPanelToggle(e, column) { e.stopPropagation(); if (this._filterPanelOpened === column.id) { this._filterPanelOpened = null; } else { const rect = e.currentTarget.getBoundingClientRect(); let left = rect.left; if (left + 328 > window.innerWidth) { left = window.innerWidth - 328; } this._filterPanelPos = { top: rect.bottom + 4, left }; this._filterPanelOpened = column.id; if (column.facetable) { this._filterPanelTab = 'counts'; } else { this._filterPanelTab = 'filter'; } } } _handleKqlClear(column) { column.filter.clear(); this._page = 1; this._fetch(); } _handleFilterClear() { const column = this._model.columns.find(c => c.id === this._filterPanelOpened); if (column) { column.filter.clear(); if (column.facetable && !column.filterable) { column.filter.operator = 'in'; column.filter.value = []; } } this._filterPanelOpened = null; this._page = 1; this._fetch(); } _handleFilterTextKeydown(e, column) { if (e.key === 'Enter') { e.preventDefault(); this._handleFilterApply(); } } _handleOperatorChange(e, column) { column.filter.setOperator(e.target.value); this.requestUpdate(); } _handleFilterStringChange(e, column) { column.filter.setValue(e.target.value); this.requestUpdate(); } _handleFilterNumberChange(e, column) { column.filter.setValue(Number(e.target.value)); this.requestUpdate(); } _handleFilterDateChange(e, column) { column.filter.setValue(new Date(e.target.value), 'day'); this.requestUpdate(); } _handleFilterBooleanChange(e, column) { column.filter.setValue(e.target.value === 'true'); this.requestUpdate(); } _handleFilterDateStartChange(e, column) { column.filter.setStart(new Date(e.target.value), 'day'); this.requestUpdate(); } _handleFilterDateEndChange(e, column) { column.filter.setEnd(new Date(e.target.value), 'day'); this.requestUpdate(); } _handleFilterNumberStartChange(e, column) { column.filter.setStart(Number(e.target.value)); this.requestUpdate(); } _handleFilterNumberEndChange(e, column) { column.filter.setEnd(Number(e.target.value)); this.requestUpdate(); } _handleFilterListChange(e, column) { const items = e.target.value.split(',').map((v) => v.trim()).filter((v) => v !== ''); if (column.type === 'number') { column.filter.setValue(items.map((v) => Number(v))); } else { column.filter.setValue(items); } this.requestUpdate(); } _handleFilterApply() { this._filterPanelOpened = null; this._page = 1; this._fetch(); } _handleFilterPanelTabChange(e) { this._filterPanelTab = e.detail.activeTabId; } _handleBucketToggle(e, column, bucket) { column.filter.toggle(bucket.val); this._page = 1; this._fetch(); } // ---------------------------------------------------------------------------- // Rendering // ---------------------------------------------------------------------------- _renderCellContent(column, row, rowIndex) { const value = row[column.id]; if (column.render) { // Use slot to project content from light DOM so external styles apply return b `<slot name=\"cell-${rowIndex}-${column.id}\"></slot>`; } if (value === null || value === undefined) { return ''; } switch (column.type) { case 'number': if (column.format === 'currency' && typeof value === 'number') { return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' }); } return String(value); case 'date': { let date; if (value instanceof Date) { date = value; } else if (typeof value === 'string' && /^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}/.test(value)) { // MySQL datetime format (UTC): \"2026-01-28 01:33:44:517\" // Replace last colon before ms with dot, append Z for UTC const isoString = value.replace(/(\\d{2}:\\d{2}:\\d{2}):(\\d+)$/, '$1.$2').replace(' ', 'T') + 'Z'; date = new Date(isoString); } else { date = new Date(value); } // Show date and time for datetime values in UTC return date.toLocaleString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', timeZone: 'UTC' }); } case 'boolean': if (value === true) return 'Yes'; if (value === false) return 'No'; return ''; default: return String(value); } } /** * Returns CSS classes for a header cell based on column config. */ _getHeaderCellClasses(column, index) { return { 'header-cell': true, 'header-cell--align-center': column.align === 'center', 'header-cell--align-right': column.align === 'right', 'header-cell--sticky-left': column.sticky === 'left', 'header-cell--sticky-left-last': column.sticky === 'left' && !this.getDisplayedColumns().slice(index + 1).some(c => c.sticky === 'left'), 'header-cell--sticky-right': column.sticky === 'right', 'header-cell--sticky-right-first': column.sticky === 'right' && !this.getDisplayedColumns().slice(0, index).some(c => c.sticky === 'right') }; } /** * Returns CSS classes for a table cell based on column config: * - Alignment (center, right) * - Sticky positioning (left, right) * - Border classes for the last left-sticky or first right-sticky column */ _getCellClasses(column, index) { return { 'cell': true, 'cell--actions': column.type === 'actions', 'cell--align-center': column.align === 'center', 'cell--align-right': column.align === 'right', 'cell--sticky-left': column.sticky === 'left', 'cell--sticky-left-last': column.sticky === 'left' && !this.getDisplayedColumns().slice(index + 1).some(c => c.sticky === 'left'), 'cell--sticky-right': column.sticky === 'right', 'cell--sticky-right-first': column.sticky === 'right' && !this.getDisplayedColumns().slice(0, index).some(c => c.sticky === 'right') }; } /** * Returns inline styles for a table cell: * - Width (from column config or default 150px) * - Min-width (if specified) * - Left/right offset for sticky columns (calculated from widths of preceding sticky columns) */ _getCellStyle(column, index) { const styles = {}; if (column.sticky === 'left') { let leftOffset = 0; for (let i = 0; i < index; i++) { const col = this.getDisplayedColumns()[i]; if (col.sticky === 'left') { leftOffset += parseInt(col.width || '0', 10); } } styles.left = `${leftOffset}px`; } if (column.sticky === 'right') { let rightOffset = 0; for (let i = index + 1; i < this.getDisplayedColumns().length; i++) { const col = this.getDisplayedColumns()[i]; if (col.sticky === 'right') { rightOffset += parseInt(col.width || '0', 10); } } styles.right = `${rightOffset}px`; } return styles; } /** * Renders the pagination controls: * - Previous page arrow (disabled on first page) * - Range text showing \"1-50 of 150\" format * - Next page arrow (disabled on last page) * * Hidden when there's no data or all data fits on one page. */ _renderPagination() { const start = (this._page - 1) * this._pageSize + 1; const end = Math.min(this._page * this._pageSize, this._totalItems); return b ` <div class=\"pagination\"> <span class=\"pagination-icon ${this._page === 1 ? 'pagination-icon--disabled' : ''}\" @click=${this.goToPrevPage} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\"/></svg> </span> <span class=\"pagination-info\">${start}-${end} of ${this._totalItems}</span> <span class=\"pagination-icon ${this._page === this._totalPages ? 'pagination-icon--disabled' : ''}\" @click=${this.goToNextPage} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\"/></svg> </span> </div> `; } /** * Renders the header toolbar containing: * - Title (left) * - Search bar with view selector dropdown (center) * - Tools (right): page navigation, refresh button, column visibility picker, actions dropdown * * Hidden when there's no title, no actions, and data fits on one page. */ _renderHeader() { if (!this._model.title && !this._model.actions?.length && this._totalPages <= 1) { return A; } return b ` <div class=\"header\"> <div class=\"title\">${this._model.title ?? ''}</div> ${this._model.dataSource?.mode === 'db' && !this._model.columns.some(col => col.searchable) ? b `<div class=\"search\"></div>` : b ` <div class=\"search\"> <!-- TODO: Saved views dropdown <div class=\"views\"> <span>Default View</span> <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z\"/></svg> </div> --> <div class=\"search-field\"> <svg class=\"search-icon\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z\"/></svg> <input type=\"text\" class=\"search-input\" placeholder=\"Search...\" .value=${this._searchQuery} @input=${this._handleSearch} /> </div> </div> `} <div class=\"tools\"> ${this._renderPagination()} <span class=\"refresh\" title=\"Refresh\" @click=${() => this.refresh()}> <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z\"/></svg> </span> <div class=\"column-picker-wrapper\"> <span class=\"header-icon\" title=\"Columns\" @click=${this._toggleColumnPicker}> <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M121-280v-400q0-33 23.5-56.5T201-760h559q33 0 56.5 23.5T840-680v400q0 33-23.5 56.5T760-200H201q-33 0-56.5-23.5T121-280Zm79 0h133v-400H200v400Zm213 0h133v-400H413v400Zm213 0h133v-400H626v400Z\"/></svg> </span> <div class=\"column-picker ${this._columnPickerOpen ? 'open' : ''}\"> ${[...this._model.columns].filter(col => col.type !== 'actions').sort((a, b) => (a.label ?? a.id).localeCompare(b.label ?? b.id)).map(col => b ` <div class=\"column-picker-item\" @click=${() => this._toggleColumn(col.id)}> <div class=\"column-picker-checkbox ${this._model.displayedColumns.includes(col.id) ? 'checked' : ''}\"> <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/></svg> </div> <span class=\"column-picker-label\">${col.label ?? col.id}</span> </div> `)} </div> </div> ${this._model.actions?.length === 1 ? b ` <kr-button class=\"actions\" .href=${this._model.actions[0].href} .target=${this._model.actions[0].target} @click=${() => this._handleAction(this._model.actions[0])} > ${this._model.actions[0].label} </kr-button> ` : this._model.actions?.length ? b ` <kr-button class=\"actions\" .options=${this._model.actions.map(a => ({ id: a.id, label: a.label }))} @option-select=${(e) => this._handleAction({ id: e.detail.id, label: e.detail.label })} > Actions </kr-button> ` : A} </div> </div> `; } /** Renders status message (loading, error, empty) */ _renderStatus() { if (this._dataState === 'loading' && this._data.length === 0) { return b `<div class=\"status\">Loading...</div>`; } if (this._dataState === 'error' && this._data.length === 0) { return b `<div class=\"status status--error\">Error loading data</div>`; } if (this._data.length === 0) { return b `<div class=\"status\">No data available</div>`; } return A; } _renderFilterPanel() { if (!this._filterPanelOpened) { return A; } const column = this._model.columns.find(c => c.id === this._filterPanelOpened); // Build filter content (operator + value input) let valueInput = b ``; if (column.filter.operator === 'empty' || column.filter.operator === 'n_empty') { valueInput = b ` <input type=\"text\" class=\"filter-panel__input\" disabled .value=${column.filter.text} /> `; } else if (column.filter.operator === 'between' && column.type === 'date') { valueInput = b ` <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${column.filter.value?.start ?? null} @change=${(e) => this._handleFilterDateStartChange(e, column)} /> <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${column.filter.value?.end ?? null} @change=${(e) => this._handleFilterDateEndChange(e, column)} /> `; } else if (column.filter.operator === 'between' && column.type === 'number') { valueInput = b ` <input type=\"number\" class=\"filter-panel__input\" placeholder=\"Start\" .value=${column.filter.value?.start ?? ''} @input=${(e) => this._handleFilterNumberStartChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> <input type=\"number\" class=\"filter-panel__input\" placeholder=\"End\" .value=${column.filter.value?.end ?? ''} @input=${(e) => this._handleFilterNumberEndChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> `; } else if (column.filter.operator === 'in') { valueInput = b ` <textarea class=\"filter-panel__textarea\" rows=\"3\" placeholder=\"Values (comma-separated)\" .value=${column.filter.text} @input=${(e) => this._handleFilterListChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} ></textarea> `; } else if (column.type === 'boolean') { valueInput = b ` <kr-select-field placeholder=\"Value\" .value=${String(column.filter.value ?? '')} @change=${(e) => this._handleFilterBooleanChange(e, column)} > <kr-select-option value=\"true\">Yes</kr-select-option> <kr-select-option value=\"false\">No</kr-select-option> </kr-select-field> `; } else if (column.type === 'date') { valueInput = b ` <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${column.filter.value} @change=${(e) => this._handleFilterDateChange(e, column)} /> `; } else if (column.type === 'number') { valueInput = b ` <input type=\"number\" class=\"filter-panel__input\" placeholder=\"Value\" min=\"0\" .value=${column.filter.text} @input=${(e) => this._handleFilterNumberChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> `; } else { valueInput = b ` <input type=\"text\" class=\"filter-panel__input\" placeholder=\"Value\" .value=${column.filter.text} @input=${(e) => this._handleFilterStringChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> `; } const filterContent = b ` <div class=\"filter-panel__content\"> <kr-select-field .value=${column.filter.operator} @change=${(e) => this._handleOperatorChange(e, column)} > ${getOperatorsForType(column.type).map(op => b ` <kr-select-option value=${op.key}>${op.label}</kr-select-option> `)} </kr-select-field> ${valueInput} </div> `; // Build bucket list content const buckets = this._buckets.get(column.id) || []; let bucketContent; if (!buckets.length) { bucketContent = b `<div class=\"bucket-empty\">No data</div>`; } else { bucketContent = b ` <div class=\"buckets\"> ${buckets.map(bucket => { let bucketLabel = '(Empty)'; if (bucket.val !== null && bucket.val !== undefined) { if (column.type === 'boolean') { if (bucket.val === true || bucket.val === 'true') { bucketLabel = 'Yes'; } else { bucketLabel = 'No'; } } else { bucketLabel = String(bucket.val); } } let checkIcon = A; if (column.filter.has(bucket.val)) { checkIcon = b ` <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/> </svg> `; } return b ` <div class=\"bucket\" @click=${(e) => this._handleBucketToggle(e, column, bucket)} > <div class=${e$1({ 'bucket__checkbox': true, 'bucket__checkbox--checked': column.filter.has(bucket.val) })}> ${checkIcon} </div> <span class=\"bucket__label\">${bucketLabel}</span> <span class=\"bucket__count\">${bucket.count}</span> </div> `; })} </div> `; } // Build panel body — tabs if both filterable+facetable, otherwise just the relevant content let panelBody; if (column.facetable && column.filterable) { panelBody = b ` <kr-tab-group size=\"small\" active-tab-id=${this._filterPanelTab} @tab-change=${(e) => this._handleFilterPanelTabChange(e)} > <kr-tab id=\"filter\" label=\"Filter\"> ${filterContent} </kr-tab> <kr-tab id=\"counts\" label=\"Counts\"> ${bucketContent} </kr-tab> </kr-tab-group> `; } else if (column.facetable) { panelBody = bucketContent; } else { panelBody = filterContent; } return b ` <div class=\"filter-panel\" style=${o$1({ top: this._filterPanelPos.top + 'px', left: this._filterPanelPos.left + 'px' })} > ${panelBody} <div class=\"filter-panel__actions\"> <kr-button variant=\"outline\" color=\"secondary\" size=\"small\" @click=${this._handleFilterClear}> Clear </kr-button> <kr-button size=\"small\" @click=${this._handleFilterApply}> Apply </kr-button> </div> </div> `; } /** * Renders filter row below column headers. * Only displays for columns with filterable: true. */ _renderFilterRow() { const columns = this.getDisplayedColumns(); if (!columns.some(col => col.filterable || col.facetable)) { return A; } return b ` <div class=\"filter-row\"> ${columns.map((col, i) => { if (!col.filterable && !col.facetable) { return b `<div class=${e$1({ 'filter-cell': true, 'filter-cell--sticky-left': col.sticky === 'left', 'filter-cell--sticky-right': col.sticky === 'right', 'filter-cell--sticky-right-first': col.sticky === 'right' && !columns.slice(0, i).some((c) => c.sticky === 'right') })} style=${o$1(this._getCellStyle(col, i))} ></div>`; } return b ` <div class=${e$1({ 'filter-cell': true, 'filter-cell--sticky-left': col.sticky === 'left', 'filter-cell--sticky-right': col.sticky === 'right', 'filter-cell--sticky-right-first': col.sticky === 'right' && !columns.slice(0, i).some((c) => c.sticky === 'right') })} style=${o$1(this._getCellStyle(col, i))} > <div class=\"filter-cell__wrapper\"> <input type=\"text\" class=${e$1({ 'filter-cell__input': true, 'filter-cell__input--invalid': !col.filter.isValid() })} .value=${col.filter.kql} @change=${(e) => this._handleKqlChange(e, col)} /> ${col.filter?.kql?.length > 0 ? b ` <button class=\"filter-cell__clear\" @click=${() => this._handleKqlClear(col)} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/> </svg> </button> ` : A} <button class=${e$1({ 'filter-cell__advanced': true, 'filter-cell__advanced--opened': this._filterPanelOpened === col.id })} @click=${(e) => this._handleFilterPanelToggle(e, col)} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z\"/> </svg> </button> </div> </div> `; })} </div> `; } /** Renders the scrollable data grid with column headers and rows. */ _renderTable() { return b ` <div class=\"wrapper\"> <div class=\"overlay-left\"></div> <div class=\"overlay-right\"></div> ${this._renderStatus()} <div class=\"content\" @scroll=${this._handleScroll}> <div class=\"table\" style=\"grid-template-columns: ${this._getGridTemplateColumns()}\"> <div class=\"header-row\"> ${this.getDisplayedColumns().map((col, i) => b ` <div class=${e$1(this._getHeaderCellClasses(col, i))} style=${o$1(this._getCellStyle(col, i))} data-column-id=${col.id} >${col.label ?? col.id}${col.resizable !== false ? b `<div class=\"header-cell__resize\" @mousedown=${(e) => this._handleResizeStart(e, col.id)} ></div>` : A}</div> `)} </div> ${this._renderFilterRow()} ${this._data.map((row, rowIndex) => { const cells = this.getDisplayedColumns().map((col, i) => b ` <div class=${e$1(this._getCellClasses(col, i))} style=${o$1(this._getCellStyle(col, i))} data-column-id=${col.id} > ${this._renderCellContent(col, row, rowIndex)} </div> `); if (this._model.rowHref) { return b ` <a href=${this._model.rowHref(row)} class=${e$1({ 'row': true, 'row--clickable': true, 'row--link': true })} @mousedown=${() => this._handleRowMouseDown()} @click=${() => this._handleRowClick(row, rowIndex)} >${cells}</a> `; } return b ` <div class=${e$1({ 'row': true, 'row--clickable': !!this._model.rowClickable })} @mousedown=${() => this._handleRowMouseDown()} @click=${() => this._handleRowClick(row, rowIndex)} >${cells}</div> `; })} </div> </div> </div> `; } /** * Renders a data table with: * - Header bar with title, search input with view selector, and tools (pagination, refresh, column visibility, actions dropdown) * - Scrollable grid with sticky header row and optional sticky left/right columns * - Loading, error message, or empty state when no data */ render() { if (!this._model.columns.length) { return b `<slot></slot>`; } return b ` ${this._renderHeader()} ${this._renderTable()} ${this._renderFilterPanel()} `; } }"
569
577
  },
570
578
  {
571
579
  "kind": "variable",
@@ -605,7 +613,12 @@
605
613
  {
606
614
  "kind": "variable",
607
615
  "name": "KRAutoSuggest",
608
- "default": "class KRAutoSuggest extends i$2 { constructor() { super(); this.label = ''; this.name = ''; this.value = ''; this.placeholder = ''; this.disabled = false; this.readonly = false; this.required = false; this.hint = ''; this.options = []; this.statusType = 'finished'; this.loadingText = 'Loading...'; this.errorText = 'Error loading options'; this.emptyText = 'No matches found'; this.filteringType = 'auto'; this.highlightMatches = true; this._isOpen = false; this._highlightedIndex = -1; this._touched = false; this._dirty = false; this._handleDocumentClick = this._onDocumentClick.bind(this); this._internals = this.attachInternals(); } connectedCallback() { super.connectedCallback(); document.addEventListener('click', this._handleDocumentClick); } disconnectedCallback() { super.disconnectedCallback(); document.removeEventListener('click', this._handleDocumentClick); } firstUpdated() { this._updateFormValue(); } get form() { return this._internals.form; } get validity() { return this._internals.validity; } get validationMessage() { return this._internals.validationMessage; } checkValidity() { return this._internals.checkValidity(); } reportValidity() { return this._internals.reportValidity(); } formResetCallback() { this.value = ''; this._touched = false; this._dirty = false; this._isOpen = false; this._highlightedIndex = -1; this._updateFormValue(); } formStateRestoreCallback(state) { this.value = state; } get _filteredOptions() { if (this.filteringType === 'manual' || !this.value) { return this.options; } const searchValue = this.value.toLowerCase(); const filtered = []; for (const option of this.options) { if (isGroup(option)) { const filteredGroupOptions = option.options.filter((opt) => { const label = (opt.label || opt.value).toLowerCase(); const tags = opt.filteringTags?.join(' ').toLowerCase() || ''; return label.includes(searchValue) || tags.includes(searchValue); }); if (filteredGroupOptions.length > 0) { filtered.push({ ...option, options: filteredGroupOptions }); } } else { const label = (option.label || option.value).toLowerCase(); const tags = option.filteringTags?.join(' ').toLowerCase() || ''; if (label.includes(searchValue) || tags.includes(searchValue)) { filtered.push(option); } } } return filtered; } get _flatOptions() { const flat = []; for (const option of this._filteredOptions) { if (isGroup(option)) { flat.push(...option.options); } else { flat.push(option); } } return flat; } _updateFormValue() { this._internals.setFormValue(this.value); // Validation if (this.required && !this.value) { this._internals.setValidity({ valueMissing: true }, 'This field is required', this._input); } else { this._internals.setValidity({}); } } _onInput(e) { const target = e.target; this.value = target.value; this._dirty = true; this._isOpen = true; this._highlightedIndex = -1; this._updateFormValue(); this.dispatchEvent(new CustomEvent('change', { detail: { value: this.value }, bubbles: true, composed: true, })); if (this.filteringType === 'manual') { this.dispatchEvent(new CustomEvent('load-items', { detail: { filteringText: this.value }, bubbles: true, composed: true, })); } } _onFocus() { this._isOpen = true; if (this.filteringType === 'manual') { this.dispatchEvent(new CustomEvent('load-items', { detail: { filteringText: this.value }, bubbles: true, composed: true, })); } } _onBlur() { this._touched = true; // Delay to allow click on option setTimeout(() => { this._isOpen = false; this._highlightedIndex = -1; }, 200); this._updateFormValue(); } _onKeyDown(e) { const options = this._flatOptions; switch (e.key) { case 'ArrowDown': e.preventDefault(); this._isOpen = true; this._highlightedIndex = Math.min(this._highlightedIndex + 1, options.length - 1); this._scrollToHighlighted(); break; case 'ArrowUp': e.preventDefault(); this._highlightedIndex = Math.max(this._highlightedIndex - 1, -1); this._scrollToHighlighted(); break; case 'Enter': if (this._highlightedIndex >= 0 && options[this._highlightedIndex]) { e.preventDefault(); this._selectOption(options[this._highlightedIndex]); } break; case 'Escape': e.preventDefault(); this._isOpen = false; this._highlightedIndex = -1; break; case 'Tab': this._isOpen = false; this._highlightedIndex = -1; break; } } _scrollToHighlighted() { this.updateComplete.then(() => { const container = this.shadowRoot?.querySelector('.dropdown'); const highlighted = this.shadowRoot?.querySelector('.option.is-highlighted'); if (container && highlighted) { const containerRect = container.getBoundingClientRect(); const highlightedRect = highlighted.getBoundingClientRect(); if (highlightedRect.bottom > containerRect.bottom) { highlighted.scrollIntoView({ block: 'nearest' }); } else if (highlightedRect.top < containerRect.top) { highlighted.scrollIntoView({ block: 'nearest' }); } } }); } _selectOption(option) { if (option.disabled) return; this.value = option.label || option.value; this._isOpen = false; this._highlightedIndex = -1; this._dirty = true; this._updateFormValue(); this.dispatchEvent(new CustomEvent('select', { detail: { value: option.value, selectedOption: option }, bubbles: true, composed: true, })); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); } _handleClear() { this.value = ''; this._updateFormValue(); this.dispatchEvent(new CustomEvent('change', { detail: { value: '' }, bubbles: true, composed: true, })); this._input?.focus(); } _onDocumentClick(e) { const path = e.composedPath(); if (!path.includes(this)) { this._isOpen = false; } } _highlightMatch(text) { if (!this.value || this.filteringType === 'manual' || !this.highlightMatches) { return b `${text}`; } const searchValue = this.value.toLowerCase(); const index = text.toLowerCase().indexOf(searchValue); if (index === -1) { return b `${text}`; } const before = text.slice(0, index); const match = text.slice(index, index + this.value.length); const after = text.slice(index + this.value.length); return b `${before}<span class=\"highlight\">${match}</span>${after}`; } _renderOption(option, index) { const isHighlighted = index === this._highlightedIndex; return b ` <button class=${e$1({ option: true, 'is-highlighted': isHighlighted, })} type=\"button\" role=\"option\" aria-selected=${isHighlighted} ?disabled=${option.disabled} @click=${() => this._selectOption(option)} @mouseenter=${() => { this._highlightedIndex = index; }} > <div class=\"option-content\"> <div class=\"option-label\"> ${this._highlightMatch(option.label || option.value)} ${option.labelTag ? b `<span class=\"option-tag\">${option.labelTag}</span>` : A} </div> ${option.description ? b `<div class=\"option-description\">${option.description}</div>` : A} ${option.tags && option.tags.length > 0 ? b ` <div class=\"option-tags\"> ${option.tags.map((tag) => b `<span class=\"option-tag\">${tag}</span>`)} </div> ` : A} </div> </button> `; } _renderDropdownContent() { if (this.statusType === 'loading') { return b ` <div class=\"status\"> <div class=\"spinner\"></div> ${this.loadingText} </div> `; } if (this.statusType === 'error') { return b ` <div class=\"status is-error\"> <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\"> <path d=\"M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z\" /> </svg> ${this.errorText} </div> `; } const filtered = this._filteredOptions; if (filtered.length === 0) { return b `<div class=\"empty\">${this.emptyText}</div>`; } let optionIndex = 0; return b ` ${filtered.map((option) => { if (isGroup(option)) { const groupOptions = option.options.map((opt) => { const rendered = this._renderOption(opt, optionIndex); optionIndex++; return rendered; }); return b ` <div class=\"group-label\">${option.label}</div> ${groupOptions} `; } const rendered = this._renderOption(option, optionIndex); optionIndex++; return rendered; })} `; } render() { const hasError = this._touched && !this.validity.valid; return b ` <div class=\"field-wrapper\"> ${this.label ? b ` <label> ${this.label} ${this.required ? b `<span class=\"required\">*</span>` : A} </label> ` : A} <div class=\"input-container\"> <div class=\"input-wrapper\"> <input type=\"text\" .value=${l(this.value)} placeholder=${o(this.placeholder || undefined)} ?disabled=${this.disabled} ?readonly=${this.readonly} ?required=${this.required} name=${o(this.name || undefined)} autocomplete=\"off\" role=\"combobox\" aria-autocomplete=\"list\" aria-expanded=${this._isOpen} aria-controls=\"dropdown\" class=${e$1({ 'input--invalid': hasError, })} @input=${this._onInput} @blur=${this._onBlur} @focus=${this._onFocus} @keydown=${this._onKeyDown} /> <div class=\"icon-container\"> ${this.value && !this.disabled && !this.readonly ? b ` <button class=\"clear-button\" type=\"button\" aria-label=\"Clear\" @click=${this._handleClear} > <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\"> <path d=\"M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z\" /> </svg> </button> ` : ''} ${!this.value && !this.disabled && !this.readonly ? b ` <svg class=\"search-icon\" fill=\"currentColor\" viewBox=\"0 0 16 16\"> <path d=\"M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z\" /> </svg> ` : ''} </div> </div> <div id=\"dropdown\" role=\"listbox\" class=${e$1({ dropdown: true, 'is-open': this._isOpen, })} > ${this._renderDropdownContent()} </div> </div> ${hasError ? b `<div class=\"validation-message\">${this.validationMessage}</div>` : this.hint ? b `<div class=\"hint\">${this.hint}</div>` : A} </div> `; } }"
616
+ "default": "class KRAutoSuggest extends i$2 { constructor() { super(); this._requestId = 0; this._handleDocumentClick = (e) => { const path = e.composedPath(); if (!path.includes(this)) { this._isOpen = false; } }; this.label = ''; this.name = ''; this.value = ''; this.placeholder = ''; this.disabled = false; this.readonly = false; this.required = false; this.hint = ''; this.options = []; this.fetch = null; this._isOpen = false; this._highlightedIndex = -1; this._touched = false; this._handleInvalid = (e) => { e.preventDefault(); this._touched = true; }; this._internals = this.attachInternals(); } connectedCallback() { super.connectedCallback(); document.addEventListener('click', this._handleDocumentClick); this.addEventListener('invalid', this._handleInvalid); } disconnectedCallback() { super.disconnectedCallback(); document.removeEventListener('click', this._handleDocumentClick); this.removeEventListener('invalid', this._handleInvalid); } firstUpdated() { this._updateValidity(); } updated(changedProperties) { if (changedProperties.has('required') || changedProperties.has('value')) { this._updateValidity(); } if (changedProperties.has('options') && this._isOpen) { this._positionDropdown(); } } get form() { return this._internals.form; } get validity() { return this._internals.validity; } get validationMessage() { return this._internals.validationMessage; } checkValidity() { return this._internals.checkValidity(); } reportValidity() { return this._internals.reportValidity(); } formResetCallback() { this.value = ''; this._touched = false; this._isOpen = false; this._highlightedIndex = -1; this._internals.setFormValue(''); this._internals.setValidity({}); } formStateRestoreCallback(state) { this.value = state; } _updateValidity() { if (this._input) { this._internals.setValidity(this._input.validity, this._input.validationMessage); } } _fetch() { if (!this.fetch) { return; } this._requestId++; const requestId = this._requestId; this.fetch(this.value).then((options) => { if (requestId === this._requestId) { this.options = options; } }).catch((error) => { console.error('kr-auto-suggest: fetch failed', error); }); } _handleInput(e) { this.value = e.target.value; this._isOpen = true; this._highlightedIndex = -1; this._positionDropdown(); this._internals.setFormValue(this.value); this._internals.setValidity(this._input.validity, this._input.validationMessage); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); this._fetch(); } _positionDropdown() { requestAnimationFrame(() => { const dropdown = this.shadowRoot?.querySelector('.dropdown'); if (!dropdown) { return; } const inputRect = this._input.getBoundingClientRect(); const spaceBelow = window.innerHeight - inputRect.bottom - 4 - 8; const spaceAbove = inputRect.top - 4 - 8; dropdown.style.left = inputRect.left + 'px'; dropdown.style.width = inputRect.width + 'px'; // Open above the input if there's more room above than below if (spaceAbove > spaceBelow) { dropdown.style.top = ''; dropdown.style.bottom = (window.innerHeight - inputRect.top + 4) + 'px'; dropdown.style.maxHeight = spaceAbove + 'px'; } else { dropdown.style.bottom = ''; dropdown.style.top = inputRect.bottom + 4 + 'px'; dropdown.style.maxHeight = spaceBelow + 'px'; } }); } _handleFocus() { this._isOpen = true; this._positionDropdown(); this._fetch(); } _handleBlur() { this._touched = true; this._internals.setValidity(this._input.validity, this._input.validationMessage); // Delay to allow click on option setTimeout(() => { this._isOpen = false; this._highlightedIndex = -1; }, 200); } _handleKeyDown(e) { switch (e.key) { case 'ArrowDown': e.preventDefault(); this._isOpen = true; this._highlightedIndex = Math.min(this._highlightedIndex + 1, this.options.length - 1); this._scrollToHighlighted(); break; case 'ArrowUp': e.preventDefault(); if (this._highlightedIndex === -1) { this._isOpen = true; this._highlightedIndex = this.options.length - 1; } else { this._highlightedIndex = Math.max(this._highlightedIndex - 1, 0); } this._scrollToHighlighted(); break; case 'Enter': if (this._highlightedIndex >= 0 && this.options[this._highlightedIndex]) { e.preventDefault(); this._selectOption(this.options[this._highlightedIndex]); } break; case 'Escape': e.preventDefault(); this._isOpen = false; this._highlightedIndex = -1; break; case 'Tab': this._isOpen = false; this._highlightedIndex = -1; break; } } _scrollToHighlighted() { this.updateComplete.then(() => { const container = this.shadowRoot?.querySelector('.dropdown'); const highlighted = this.shadowRoot?.querySelector('.option--highlighted'); if (container && highlighted) { const containerRect = container.getBoundingClientRect(); const highlightedRect = highlighted.getBoundingClientRect(); if (highlightedRect.bottom > containerRect.bottom) { highlighted.scrollIntoView({ block: 'nearest' }); } else if (highlightedRect.top < containerRect.top) { highlighted.scrollIntoView({ block: 'nearest' }); } } }); } _selectOption(option) { if (option.disabled) { return; } this.value = option.value; this._isOpen = false; this._highlightedIndex = -1; this._internals.setFormValue(this.value); this.dispatchEvent(new CustomEvent('select', { detail: { value: option.value, option: option }, bubbles: true, composed: true, })); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); } _handleClear() { this.value = ''; this._internals.setFormValue(this.value); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); this._input?.focus(); } _handleOptionMouseEnter(e, index) { this._highlightedIndex = index; } _renderOption(option, index) { return b ` <button class=${e$1({ option: true, 'option--highlighted': index === this._highlightedIndex, })} type=\"button\" role=\"option\" aria-selected=${index === this._highlightedIndex} ?disabled=${option.disabled} @click=${(e) => this._selectOption(option)} @mouseenter=${(e) => this._handleOptionMouseEnter(e, index)} > ${option.label || option.value} </button> `; } render() { let footer = A; if (this._touched && this._input && !this._input.validity.valid) { footer = b `<div class=\"validation-message\">${this._input.validationMessage}</div>`; } else if (this.hint) { footer = b `<div class=\"hint\">${this.hint}</div>`; } return b ` <div class=\"wrapper\"> ${this.label ? b ` <label> ${this.label} ${this.required ? b `<span class=\"required\">*</span>` : A} </label> ` : A} <div class=\"input-wrapper\"> <input type=\"text\" .value=${l(this.value)} placeholder=${this.placeholder} ?disabled=${this.disabled} ?readonly=${this.readonly} ?required=${this.required} name=${this.name} autocomplete=\"off\" role=\"combobox\" aria-autocomplete=\"list\" aria-expanded=${this._isOpen} aria-controls=\"dropdown\" class=${e$1({ 'input--invalid': this._touched && this._input && !this._input.validity.valid, })} @input=${this._handleInput} @blur=${this._handleBlur} @focus=${this._handleFocus} @keydown=${this._handleKeyDown} /> <div class=\"icon-wrapper\"> ${this.value && !this.disabled && !this.readonly ? b ` <svg class=\"clear\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-label=\"Clear\" @click=${this._handleClear}> <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/> </svg> ` : A} ${!this.value && !this.disabled && !this.readonly ? b ` <svg class=\"search-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z\"/> </svg> ` : A} </div> </div> <div id=\"dropdown\" role=\"listbox\" class=${e$1({ dropdown: true, 'dropdown--open': this._isOpen && this.options.length > 0, })} > ${this.options.map((option, index) => this._renderOption(option, index))} </div> ${footer} </div> `; } }"
617
+ },
618
+ {
619
+ "kind": "variable",
620
+ "name": "KRComboBox",
621
+ "default": "class KRComboBox extends i$2 { constructor() { super(); this._requestId = 0; this._selectedOption = null; this._handleDocumentClick = (e) => { if (!e.composedPath().includes(this)) { this._close(); } }; this.label = ''; this.name = ''; this.value = ''; this.placeholder = 'Select an option'; this.disabled = false; this.readonly = false; this.required = false; this.hint = ''; this.optionValue = 'value'; this.optionLabel = 'label'; this.options = []; this.fetch = null; this.fetchSelection = null; this._isOpen = false; this._highlightedIndex = -1; this._touched = false; this._searchQuery = ''; this._handleInvalid = (e) => { e.preventDefault(); this._touched = true; }; this._internals = this.attachInternals(); } connectedCallback() { super.connectedCallback(); document.addEventListener('click', this._handleDocumentClick); this.addEventListener('invalid', this._handleInvalid); } disconnectedCallback() { super.disconnectedCallback(); document.removeEventListener('click', this._handleDocumentClick); this.removeEventListener('invalid', this._handleInvalid); } firstUpdated() { this._updateValidity(); if (this.value && this.fetchSelection) { this._resolveSelection(); } } updated(changedProperties) { if (changedProperties.has('required') || changedProperties.has('value')) { this._updateValidity(); } if (changedProperties.has('value')) { if (this.value) { // Only resolve if the selected option doesn't match the current value if (!this._selectedOption || this._getOptionValue(this._selectedOption) !== this.value) { this._resolveSelection(); } } else { this._selectedOption = null; } } if (changedProperties.has('options') && this._isOpen) { this._positionDropdown(); } } get form() { return this._internals.form; } get validity() { return this._internals.validity; } get validationMessage() { return this._internals.validationMessage; } checkValidity() { return this._internals.checkValidity(); } reportValidity() { return this._internals.reportValidity(); } formResetCallback() { this.value = ''; this._selectedOption = null; this._touched = false; this._isOpen = false; this._highlightedIndex = -1; this._searchQuery = ''; this._internals.setFormValue(''); this._internals.setValidity({}); } formStateRestoreCallback(state) { this.value = state; } focus() { this._triggerElement?.focus(); } blur() { this._triggerElement?.blur(); } _updateValidity() { if (this.required && !this.value) { this._internals.setValidity({ valueMissing: true }, 'Please select an option', this._triggerElement); } else { this._internals.setValidity({}); } } _handleTriggerClick() { if (this.disabled || this.readonly) { return; } if (this._isOpen) { this._close(); } else { this._open(); } } _open() { this._isOpen = true; this._searchQuery = ''; this._highlightedIndex = -1; this._fetch(); this.updateComplete.then(() => { this._positionDropdown(); if (this._searchInput) { this._searchInput.focus(); } }); } _close() { this._isOpen = false; this._highlightedIndex = -1; this._searchQuery = ''; } _positionDropdown() { requestAnimationFrame(() => { const dropdown = this.shadowRoot?.querySelector('.dropdown'); if (!dropdown) { return; } const triggerRect = this._triggerElement.getBoundingClientRect(); const spaceBelow = window.innerHeight - triggerRect.bottom - 4 - 8; const spaceAbove = triggerRect.top - 4 - 8; dropdown.style.left = triggerRect.left + 'px'; dropdown.style.width = triggerRect.width + 'px'; // Prefer opening below; only flip above when space below is tight if (spaceBelow < 200 && spaceAbove > spaceBelow) { dropdown.style.top = ''; dropdown.style.bottom = (window.innerHeight - triggerRect.top + 4) + 'px'; dropdown.style.maxHeight = spaceAbove + 'px'; } else { dropdown.style.bottom = ''; dropdown.style.top = triggerRect.bottom + 4 + 'px'; dropdown.style.maxHeight = spaceBelow + 'px'; } }); } _fetch() { if (!this.fetch) { return; } this._requestId++; const requestId = this._requestId; this.fetch(this._searchQuery).then((options) => { if (requestId === this._requestId) { this.options = options; } }).catch((error) => { console.error('kr-combo-box: fetch failed', error); }); } _handleSearchInput(e) { this._searchQuery = e.target.value; this._highlightedIndex = -1; this._fetch(); } _handleSearchKeyDown(e) { switch (e.key) { case 'ArrowDown': e.preventDefault(); this._highlightedIndex = Math.min(this._highlightedIndex + 1, this.options.length - 1); this._scrollToHighlighted(); break; case 'ArrowUp': e.preventDefault(); if (this._highlightedIndex === -1) { this._highlightedIndex = this.options.length - 1; } else { this._highlightedIndex = Math.max(this._highlightedIndex - 1, 0); } this._scrollToHighlighted(); break; case 'Enter': e.preventDefault(); if (this._highlightedIndex >= 0 && this.options[this._highlightedIndex]) { this._selectOption(this.options[this._highlightedIndex]); } break; case 'Escape': e.preventDefault(); this._close(); this._triggerElement?.focus(); break; case 'Tab': this._close(); break; } } _handleTriggerKeyDown(e) { if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if (!this._isOpen) { this._open(); } } } _handleTriggerBlur() { this._touched = true; this._updateValidity(); } _scrollToHighlighted() { this.updateComplete.then(() => { const container = this.shadowRoot?.querySelector('.options'); const highlighted = this.shadowRoot?.querySelector('.option--highlighted'); if (container && highlighted) { const containerRect = container.getBoundingClientRect(); const highlightedRect = highlighted.getBoundingClientRect(); if (highlightedRect.bottom > containerRect.bottom) { highlighted.scrollIntoView({ block: 'nearest' }); } else if (highlightedRect.top < containerRect.top) { highlighted.scrollIntoView({ block: 'nearest' }); } } }); } _getOptionValue(option) { if (typeof this.optionValue === 'function') { return this.optionValue(option); } return option[this.optionValue]; } _getOptionLabel(option) { let label; if (typeof this.optionLabel === 'function') { label = this.optionLabel(option); } else { label = option[this.optionLabel]; } if (!label) { return this._getOptionValue(option); } return label; } _resolveSelection() { if (!this.fetchSelection) { return; } const fetchedValue = this.value; this.fetchSelection(this.value).then((option) => { if (this.value !== fetchedValue) { return; } if (!option) { return; } this._selectedOption = option; this.requestUpdate(); }).catch((error) => { console.error('kr-combo-box: fetchSelection failed', error); }); } _selectOption(option) { if (option.disabled) { return; } this.value = this._getOptionValue(option); this._selectedOption = option; this._close(); this._internals.setFormValue(this.value); this._updateValidity(); this.dispatchEvent(new CustomEvent('select', { detail: { option: option }, bubbles: true, composed: true, })); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); this._triggerElement?.focus(); } _handleOptionMouseEnter(e, index) { this._highlightedIndex = index; } _renderOption(option, index) { const optionValue = this._getOptionValue(option); return b ` <button class=${e$1({ option: true, 'option--highlighted': index === this._highlightedIndex, 'option--selected': optionValue === this.value, })} type=\"button\" role=\"option\" aria-selected=${optionValue === this.value} ?disabled=${option.disabled} @click=${(e) => this._selectOption(option)} @mouseenter=${(e) => this._handleOptionMouseEnter(e, index)} > ${this._getOptionLabel(option)} </button> `; } render() { let footer = A; if (this._touched && this.required && !this.value) { footer = b `<div class=\"validation-message\">Please select an option</div>`; } else if (this.hint) { footer = b `<div class=\"hint\">${this.hint}</div>`; } return b ` <div class=\"wrapper\"> ${this.label ? b ` <label> ${this.label} ${this.required ? b `<span class=\"required\">*</span>` : A} </label> ` : A} <button class=${e$1({ trigger: true, 'trigger--open': this._isOpen, 'trigger--invalid': this._touched && this.required && !this.value, })} type=\"button\" ?disabled=${this.disabled} aria-haspopup=\"listbox\" aria-expanded=${this._isOpen} @click=${this._handleTriggerClick} @keydown=${this._handleTriggerKeyDown} @blur=${this._handleTriggerBlur} > <span class=${e$1({ trigger__value: true, trigger__placeholder: !this._selectedOption, })}> ${this._selectedOption ? this._getOptionLabel(this._selectedOption) : this.placeholder} </span> <svg class=${e$1({ trigger__icon: true, 'trigger__icon--open': this._isOpen, })} viewBox=\"0 0 24 24\" fill=\"currentColor\" > <path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z\"/> </svg> </button> <div role=\"listbox\" class=${e$1({ dropdown: true, 'dropdown--open': this._isOpen, })} > <div class=\"search\"> <div class=\"search__field\"> <input class=\"search__input\" type=\"text\" placeholder=\"Search...\" .value=${this._searchQuery} @input=${this._handleSearchInput} @keydown=${this._handleSearchKeyDown} /> <svg class=\"search__icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z\"/> </svg> </div> </div> <div class=\"options\"> ${this.options.length === 0 ? b `<div class=\"empty\">No options found</div>` : this.options.map((option, index) => this._renderOption(option, index))} </div> </div> ${footer} </div> `; } }"
609
622
  }
610
623
  ],
611
624
  "exports": [
@@ -665,6 +678,14 @@
665
678
  "module": "dist/krubble-components.bundled.js"
666
679
  }
667
680
  },
681
+ {
682
+ "kind": "js",
683
+ "name": "KRComboBox",
684
+ "declaration": {
685
+ "name": "KRComboBox",
686
+ "module": "dist/krubble-components.bundled.js"
687
+ }
688
+ },
668
689
  {
669
690
  "kind": "js",
670
691
  "name": "KRContextMenu",
@@ -840,27 +861,27 @@
840
861
  {
841
862
  "kind": "variable",
842
863
  "name": "ve",
843
- "default": "class extends ae{constructor(){super(...arguments),this.header=\"\",this.expanded=!1}toggle(){this.expanded=!this.expanded}render(){return V` <div class=\"header\" @click=${this.toggle}> <span class=\"header__title\">${this.header}</span> <svg class=\"header__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"> <path d=\"M6 9l6 6 6-6\"/> </svg> </div> <div class=\"content\"> <div class=\"content__inner\"> <div class=\"content__body\"> <slot></slot> </div> </div> </div> `}}"
864
+ "default": "class extends le{constructor(){super(...arguments),this.header=\"\",this.expanded=!1}toggle(){this.expanded=!this.expanded}render(){return V` <div class=\"header\" @click=${this.toggle}> <span class=\"header__title\">${this.header}</span> <svg class=\"header__icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"> <path d=\"M6 9l6 6 6-6\"/> </svg> </div> <div class=\"content\"> <div class=\"content__inner\"> <div class=\"content__body\"> <slot></slot> </div> </div> </div> `}}"
844
865
  },
845
866
  {
846
867
  "kind": "variable",
847
868
  "name": "Ce",
848
- "default": "class extends ae{constructor(){super(...arguments),this.type=\"info\",this.title=\"\",this.dismissible=!1,this.visible=!0}_handleDismiss(){this.visible=!1,this.dispatchEvent(new CustomEvent(\"dismiss\",{bubbles:!0,composed:!0}))}render(){const e={info:V`<svg class=\"icon\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z\" clip-rule=\"evenodd\"/></svg>`,success:V`<svg class=\"icon\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clip-rule=\"evenodd\"/></svg>`,warning:V`<svg class=\"icon\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z\" clip-rule=\"evenodd\"/></svg>`,error:V`<svg class=\"icon\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z\" clip-rule=\"evenodd\"/></svg>`};return V` <div class=${we({alert:!0,[\"alert--\"+this.type]:!0,\"alert--has-header\":!!this.title,\"alert--hidden\":!this.visible})} role=\"alert\" > ${e[this.type]} <div class=\"content\"> ${this.title?V`<h4 class=\"header\">${this.title}</h4>`:B} <div class=\"message\"> <slot></slot> </div> </div> ${this.dismissible?V` <button class=\"dismiss\" type=\"button\" aria-label=\"Dismiss alert\" @click=${this._handleDismiss} > <svg viewBox=\"0 0 20 20\" fill=\"currentColor\" width=\"16\" height=\"16\"> <path fill-rule=\"evenodd\" d=\"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z\" clip-rule=\"evenodd\"/> </svg> </button> `:B} </div> `}}"
869
+ "default": "class extends le{constructor(){super(...arguments),this.type=\"info\",this.title=\"\",this.dismissible=!1,this.visible=!0}_handleDismiss(){this.visible=!1,this.dispatchEvent(new CustomEvent(\"dismiss\",{bubbles:!0,composed:!0}))}render(){const e={info:V`<svg class=\"icon\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z\" clip-rule=\"evenodd\"/></svg>`,success:V`<svg class=\"icon\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z\" clip-rule=\"evenodd\"/></svg>`,warning:V`<svg class=\"icon\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z\" clip-rule=\"evenodd\"/></svg>`,error:V`<svg class=\"icon\" viewBox=\"0 0 20 20\" fill=\"currentColor\"><path fill-rule=\"evenodd\" d=\"M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z\" clip-rule=\"evenodd\"/></svg>`};return V` <div class=${we({alert:!0,[\"alert--\"+this.type]:!0,\"alert--has-header\":!!this.title,\"alert--hidden\":!this.visible})} role=\"alert\" > ${e[this.type]} <div class=\"content\"> ${this.title?V`<h4 class=\"header\">${this.title}</h4>`:U} <div class=\"message\"> <slot></slot> </div> </div> ${this.dismissible?V` <button class=\"dismiss\" type=\"button\" aria-label=\"Dismiss alert\" @click=${this._handleDismiss} > <svg viewBox=\"0 0 20 20\" fill=\"currentColor\" width=\"16\" height=\"16\"> <path fill-rule=\"evenodd\" d=\"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z\" clip-rule=\"evenodd\"/> </svg> </button> `:U} </div> `}}"
849
870
  },
850
871
  {
851
872
  "kind": "variable",
852
873
  "name": "Ee",
853
- "default": "class extends ae{constructor(){super(...arguments),this.variant=\"flat\",this.color=\"primary\",this.size=\"medium\",this.disabled=!1,this.options=[],this.iconPosition=\"left\",this._state=\"idle\",this._stateText=\"\",this._dropdownOpened=!1,this._handleHostClick=e=>{this.options.length&&(e.stopPropagation(),this._toggleDropdown())},this._handleKeydown=e=>{\"Enter\"!==e.key&&\" \"!==e.key||(e.preventDefault(),this.options.length?this._toggleDropdown():this.click()),\"Escape\"===e.key&&this._dropdownOpened&&(this._dropdownOpened=!1)},this._handleClickOutside=e=>{this._dropdownOpened&&!this.contains(e.target)&&(this._dropdownOpened=!1)}}connectedCallback(){super.connectedCallback(),this.setAttribute(\"role\",this.href?\"link\":\"button\"),this.setAttribute(\"tabindex\",\"0\"),this.addEventListener(\"keydown\",this._handleKeydown),this.addEventListener(\"click\",this._handleHostClick),document.addEventListener(\"click\",this._handleClickOutside)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(\"keydown\",this._handleKeydown),this.removeEventListener(\"click\",this._handleHostClick),document.removeEventListener(\"click\",this._handleClickOutside)}_toggleDropdown(){this._dropdownOpened=!this._dropdownOpened,this._dropdownOpened&&requestAnimationFrame((()=>{const e=this.shadowRoot?.querySelector(\".dropdown\");if(e){const t=this.getBoundingClientRect();e.style.top=t.bottom+4+\"px\",e.style.bottom=\"\",e.style.left=t.left+\"px\",e.style.right=\"\",e.style.minWidth=t.width+\"px\",e.style.transformOrigin=\"top left\";const i=e.getBoundingClientRect();i.bottom>window.innerHeight?(e.style.top=\"\",e.style.bottom=window.innerHeight-t.top+4+\"px\",e.style.transformOrigin=\"bottom left\",e.classList.add(\"dropdown--above\")):e.classList.remove(\"dropdown--above\"),i.right>window.innerWidth&&(e.style.left=\"\",e.style.right=window.innerWidth-t.right+\"px\",i.bottom>window.innerHeight?e.style.transformOrigin=\"bottom right\":e.style.transformOrigin=\"top right\")}}))}_handleOptionClick(e,t){t.stopPropagation(),this._dropdownOpened=!1,this.dispatchEvent(new CustomEvent(\"option-select\",{detail:{id:e.id,label:e.label},bubbles:!0,composed:!0}))}showLoading(){this._clearStateTimeout(),this._state=\"loading\",this._stateText=\"\"}showSuccess(e=\"Success\",t=2e3){this._clearStateTimeout(),this._state=\"success\",this._stateText=e,t>0&&(this._stateTimeout=window.setTimeout((()=>this.reset()),t))}showError(e=\"Error\",t=2e3){this._clearStateTimeout(),this._state=\"error\",this._stateText=e,t>0&&(this._stateTimeout=window.setTimeout((()=>this.reset()),t))}isLoading(){return\"loading\"===this._state}reset(){this._clearStateTimeout(),this._state=\"idle\",this._stateText=\"\"}_clearStateTimeout(){this._stateTimeout&&(clearTimeout(this._stateTimeout),this._stateTimeout=void 0)}updated(e){this.classList.toggle(\"kr-button--loading\",\"loading\"===this._state),this.classList.toggle(\"kr-button--success\",\"success\"===this._state),this.classList.toggle(\"kr-button--error\",\"error\"===this._state),this.classList.toggle(`kr-button--${this.variant}`,!0),this.classList.toggle(`kr-button--${this.color}`,!0),this.classList.toggle(\"kr-button--small\",\"small\"===this.size),this.classList.toggle(\"kr-button--large\",\"large\"===this.size)}render(){const e=V` <span class=\"content\"> <slot name=\"icon\"></slot> <slot></slot> </span> ${this.options.length?V`<svg class=\"caret\" xmlns=\"http://www.w3.org/2000/svg\" height=\"20\" width=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z\"/></svg>`:B} ${\"idle\"!==this._state?V`<span class=\"state-overlay\"> ${\"loading\"===this._state?V`<span class=\"spinner\"></span>`:this._stateText} </span>`:B} ${this.options.length?V` <div class=\"dropdown ${this._dropdownOpened?\"dropdown--opened\":\"\"}\"> ${this.options.map((e=>V` <button class=\"dropdown-item\" @click=${t=>this._handleOptionClick(e,t)} >${e.label}</button> `))} </div> `:B} `;return this.href?V`<a class=\"link\" href=${this.href} target=${this.target||B}>${e}</a>`:e}}"
874
+ "default": "class extends le{constructor(){super(...arguments),this.variant=\"flat\",this.color=\"primary\",this.size=\"medium\",this.disabled=!1,this.options=[],this.iconPosition=\"left\",this._state=\"idle\",this._stateText=\"\",this._dropdownOpened=!1,this._handleHostClick=e=>{this.options.length&&(e.stopPropagation(),this._toggleDropdown())},this._handleKeydown=e=>{\"Enter\"!==e.key&&\" \"!==e.key||(e.preventDefault(),this.options.length?this._toggleDropdown():this.click()),\"Escape\"===e.key&&this._dropdownOpened&&(this._dropdownOpened=!1)},this._handleClickOutside=e=>{this._dropdownOpened&&!this.contains(e.target)&&(this._dropdownOpened=!1)}}connectedCallback(){super.connectedCallback(),this.setAttribute(\"role\",this.href?\"link\":\"button\"),this.setAttribute(\"tabindex\",\"0\"),this.addEventListener(\"keydown\",this._handleKeydown),this.addEventListener(\"click\",this._handleHostClick),document.addEventListener(\"click\",this._handleClickOutside)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(\"keydown\",this._handleKeydown),this.removeEventListener(\"click\",this._handleHostClick),document.removeEventListener(\"click\",this._handleClickOutside)}_toggleDropdown(){this._dropdownOpened=!this._dropdownOpened,this._dropdownOpened&&requestAnimationFrame((()=>{const e=this.shadowRoot?.querySelector(\".dropdown\");if(e){const t=this.getBoundingClientRect();e.style.top=t.bottom+4+\"px\",e.style.bottom=\"\",e.style.left=t.left+\"px\",e.style.right=\"\",e.style.minWidth=t.width+\"px\",e.style.transformOrigin=\"top left\";const i=e.getBoundingClientRect();i.bottom>window.innerHeight?(e.style.top=\"\",e.style.bottom=window.innerHeight-t.top+4+\"px\",e.style.transformOrigin=\"bottom left\",e.classList.add(\"dropdown--above\")):e.classList.remove(\"dropdown--above\"),i.right>window.innerWidth&&(e.style.left=\"\",e.style.right=window.innerWidth-t.right+\"px\",i.bottom>window.innerHeight?e.style.transformOrigin=\"bottom right\":e.style.transformOrigin=\"top right\")}}))}_handleOptionClick(e,t){t.stopPropagation(),this._dropdownOpened=!1,this.dispatchEvent(new CustomEvent(\"option-select\",{detail:{id:e.id,label:e.label},bubbles:!0,composed:!0}))}showLoading(){this._clearStateTimeout(),this._state=\"loading\",this._stateText=\"\"}showSuccess(e=\"Success\",t=2e3){this._clearStateTimeout(),this._state=\"success\",this._stateText=e,t>0&&(this._stateTimeout=window.setTimeout((()=>this.reset()),t))}showError(e=\"Error\",t=2e3){this._clearStateTimeout(),this._state=\"error\",this._stateText=e,t>0&&(this._stateTimeout=window.setTimeout((()=>this.reset()),t))}isLoading(){return\"loading\"===this._state}reset(){this._clearStateTimeout(),this._state=\"idle\",this._stateText=\"\"}_clearStateTimeout(){this._stateTimeout&&(clearTimeout(this._stateTimeout),this._stateTimeout=void 0)}updated(e){this.classList.toggle(\"kr-button--loading\",\"loading\"===this._state),this.classList.toggle(\"kr-button--success\",\"success\"===this._state),this.classList.toggle(\"kr-button--error\",\"error\"===this._state),this.classList.toggle(`kr-button--${this.variant}`,!0),this.classList.toggle(`kr-button--${this.color}`,!0),this.classList.toggle(\"kr-button--small\",\"small\"===this.size),this.classList.toggle(\"kr-button--large\",\"large\"===this.size)}render(){const e=V` <span class=\"content\"> <slot name=\"icon\"></slot> <slot></slot> </span> ${this.options.length?V`<svg class=\"caret\" xmlns=\"http://www.w3.org/2000/svg\" height=\"20\" width=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z\"/></svg>`:U} ${\"idle\"!==this._state?V`<span class=\"state-overlay\"> ${\"loading\"===this._state?V`<span class=\"spinner\"></span>`:this._stateText} </span>`:U} ${this.options.length?V` <div class=\"dropdown ${this._dropdownOpened?\"dropdown--opened\":\"\"}\"> ${this.options.map((e=>V` <button class=\"dropdown-item\" @click=${t=>this._handleOptionClick(e,t)} >${e.label}</button> `))} </div> `:U} `;return this.href?V`<a class=\"link\" href=${this.href} target=${this.target||U}>${e}</a>`:e}}"
854
875
  },
855
876
  {
856
877
  "kind": "variable",
857
878
  "name": "Pe",
858
- "default": "class extends ae{constructor(){super(...arguments),this.language=\"html\",this.code=\"\",this.activeTab=\"preview\",this.copied=!1,this.highlightedCode=\"\"}connectedCallback(){super.connectedCallback(),requestAnimationFrame((()=>{this.code||(this.code=this.innerHTML.trim().replace(/=\"\"(?=[\\s>])/g,\"\")),this.querySelectorAll(\"script\").forEach((e=>{const t=document.createElement(\"script\");t.textContent=e.textContent,e.replaceWith(t)})),this.code&&window.hljs&&window.hljs.getLanguage(this.language)?this.highlightedCode=window.hljs.highlight(this.code,{language:this.language}).value:this.highlightedCode=this.code.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\")}))}activateTab(e){this.activeTab=e}copyCode(){this.code&&navigator.clipboard.writeText(this.code).then((()=>{this.copied=!0,setTimeout((()=>{this.copied=!1}),2e3)}))}render(){return V` <div class=\"tabs\"> <button class=${we({tab:!0,\"tab--active\":\"preview\"===this.activeTab})} @click=${()=>this.activateTab(\"preview\")} > Preview </button> <button class=${we({tab:!0,\"tab--active\":\"code\"===this.activeTab})} @click=${()=>this.activateTab(\"code\")} > Code </button> <button class=${we({copy:!0,\"copy--success\":this.copied})} @click=${this.copyCode} title=${this.copied?\"Copied!\":\"Copy code\"} > ${this.copied?V`<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg>`:V`<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path></svg>`} </button> </div> <div class=${we({panel:!0,\"panel--active\":\"preview\"===this.activeTab,\"panel--preview\":!0})}> <slot></slot> </div> <div class=${we({panel:!0,\"panel--active\":\"code\"===this.activeTab,\"panel--code\":!0})}> <pre class=\"code\"><code>${Te(this.highlightedCode)}</code></pre> </div> `}}"
879
+ "default": "class extends le{constructor(){super(...arguments),this.language=\"html\",this.code=\"\",this.activeTab=\"preview\",this.copied=!1,this.highlightedCode=\"\"}connectedCallback(){super.connectedCallback(),requestAnimationFrame((()=>{this.code||(this.code=this.innerHTML.trim().replace(/=\"\"(?=[\\s>])/g,\"\")),this.querySelectorAll(\"script\").forEach((e=>{const t=document.createElement(\"script\");t.textContent=e.textContent,e.replaceWith(t)})),this.code&&window.hljs&&window.hljs.getLanguage(this.language)?this.highlightedCode=window.hljs.highlight(this.code,{language:this.language}).value:this.highlightedCode=this.code.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\")}))}activateTab(e){this.activeTab=e}copyCode(){this.code&&navigator.clipboard.writeText(this.code).then((()=>{this.copied=!0,setTimeout((()=>{this.copied=!1}),2e3)}))}render(){return V` <div class=\"tabs\"> <button class=${we({tab:!0,\"tab--active\":\"preview\"===this.activeTab})} @click=${()=>this.activateTab(\"preview\")} > Preview </button> <button class=${we({tab:!0,\"tab--active\":\"code\"===this.activeTab})} @click=${()=>this.activateTab(\"code\")} > Code </button> <button class=${we({copy:!0,\"copy--success\":this.copied})} @click=${this.copyCode} title=${this.copied?\"Copied!\":\"Copy code\"} > ${this.copied?V`<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg>`:V`<svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect><path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"></path></svg>`} </button> </div> <div class=${we({panel:!0,\"panel--active\":\"preview\"===this.activeTab,\"panel--preview\":!0})}> <slot></slot> </div> <div class=${we({panel:!0,\"panel--active\":\"code\"===this.activeTab,\"panel--code\":!0})}> <pre class=\"code\"><code>${Oe(this.highlightedCode)}</code></pre> </div> `}}"
859
880
  },
860
881
  {
861
882
  "kind": "variable",
862
883
  "name": "De",
863
- "default": "class extends ae{constructor(){super(...arguments),this.items=[],this.resolvePromise=null,this.boundHandleOutsideClick=this.handleOutsideClick.bind(this),this.boundHandleKeyDown=this.handleKeyDown.bind(this)}static async open(e){const t=document.querySelector(\"kr-context-menu\");t&&t.remove();const i=document.createElement(\"kr-context-menu\");return document.body.appendChild(i),i.show(e)}async show(e){this.items=e.items,this.style.left=`${e.x}px`,this.style.top=`${e.y}px`,await this.updateComplete;const t=this.getBoundingClientRect();return t.right>window.innerWidth&&(this.style.left=e.x-t.width+\"px\"),t.bottom>window.innerHeight&&(this.style.top=e.y-t.height+\"px\"),requestAnimationFrame((()=>{document.addEventListener(\"click\",this.boundHandleOutsideClick),document.addEventListener(\"contextmenu\",this.boundHandleOutsideClick),document.addEventListener(\"keydown\",this.boundHandleKeyDown)})),new Promise((e=>{this.resolvePromise=e}))}handleOutsideClick(e){this.contains(e.target)||this.close(null)}handleKeyDown(e){\"Escape\"===e.key&&this.close(null)}handleItemClick(e){e.disabled||e.divider||this.close(e)}close(e){document.removeEventListener(\"click\",this.boundHandleOutsideClick),document.removeEventListener(\"contextmenu\",this.boundHandleOutsideClick),document.removeEventListener(\"keydown\",this.boundHandleKeyDown),this.resolvePromise&&(this.resolvePromise(e),this.resolvePromise=null),this.remove()}render(){return V` <div class=\"menu\"> ${this.items.map((e=>e.divider?V`<div class=\"menu__divider\"></div>`:V` <button class=\"menu__item\" ?disabled=${e.disabled} @click=${()=>this.handleItemClick(e)} > ${e.icon?V`<span class=\"menu__item-icon\">${e.icon}</span>`:null} ${e.label} </button> `))} </div> `}}"
884
+ "default": "class extends le{constructor(){super(...arguments),this.items=[],this.resolvePromise=null,this.boundHandleOutsideClick=this.handleOutsideClick.bind(this),this.boundHandleKeyDown=this.handleKeyDown.bind(this)}static async open(e){const t=document.querySelector(\"kr-context-menu\");t&&t.remove();const i=document.createElement(\"kr-context-menu\");return document.body.appendChild(i),i.show(e)}async show(e){this.items=e.items,this.style.left=`${e.x}px`,this.style.top=`${e.y}px`,await this.updateComplete;const t=this.getBoundingClientRect();return t.right>window.innerWidth&&(this.style.left=e.x-t.width+\"px\"),t.bottom>window.innerHeight&&(this.style.top=e.y-t.height+\"px\"),requestAnimationFrame((()=>{document.addEventListener(\"click\",this.boundHandleOutsideClick),document.addEventListener(\"contextmenu\",this.boundHandleOutsideClick),document.addEventListener(\"keydown\",this.boundHandleKeyDown)})),new Promise((e=>{this.resolvePromise=e}))}handleOutsideClick(e){this.contains(e.target)||this.close(null)}handleKeyDown(e){\"Escape\"===e.key&&this.close(null)}handleItemClick(e){e.disabled||e.divider||this.close(e)}close(e){document.removeEventListener(\"click\",this.boundHandleOutsideClick),document.removeEventListener(\"contextmenu\",this.boundHandleOutsideClick),document.removeEventListener(\"keydown\",this.boundHandleKeyDown),this.resolvePromise&&(this.resolvePromise(e),this.resolvePromise=null),this.remove()}render(){return V` <div class=\"menu\"> ${this.items.map((e=>e.divider?V`<div class=\"menu__divider\"></div>`:V` <button class=\"menu__item\" ?disabled=${e.disabled} @click=${()=>this.handleItemClick(e)} > ${e.icon?V`<span class=\"menu__item-icon\">${e.icon}</span>`:null} ${e.label} </button> `))} </div> `}}"
864
885
  },
865
886
  {
866
887
  "kind": "class",
@@ -914,8 +935,8 @@
914
935
  },
915
936
  {
916
937
  "kind": "variable",
917
- "name": "Ue",
918
- "default": "class extends ae{constructor(){super(...arguments),this._dialogRef=null,this._contentElement=null,this.opened=!1,this.label=\"\",this.width=\"560px\",this._handleDocumentKeyDown=e=>{\"Escape\"===e.key&&this.close()}}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(\"keydown\",this._handleDocumentKeyDown)}updated(e){super.updated(e),e.has(\"opened\")&&(this.opened?document.addEventListener(\"keydown\",this._handleDocumentKeyDown):document.removeEventListener(\"keydown\",this._handleDocumentKeyDown))}open(){this.opened=!0}close(){this._dialogRef?this._dialogRef.close(void 0):(this.opened=!1,this.dispatchEvent(new CustomEvent(\"close\",{bubbles:!0,composed:!0})))}static open(e,t){document.querySelectorAll(\"kr-dialog\").forEach((e=>{e._dialogRef&&e.remove()}));const i=new Ve,s=document.createElement(\"kr-dialog\");i.setDialogElement(s),s._dialogRef=i;const o=new e;return o.dialogRef=i,t?.data&&(o.data=t.data),t?.label&&(s.label=t.label),t?.width&&(s.width=t.width),s._contentElement=o,s.opened=!0,document.body.appendChild(s),i}_handleBackdropClick(e){e.target.classList.contains(\"backdrop\")&&this.close()}render(){return V` <div class=\"backdrop\" @click=${this._handleBackdropClick}></div> <div class=\"dialog\" style=${Ie({width:this.width})}> ${this.label?V`<div class=\"dialog__header\"><div class=\"dialog__header-label\">${this.label}</div></div>`:\"\"} ${this._contentElement?this._contentElement:V`<slot></slot>`} </div> `}}"
938
+ "name": "Be",
939
+ "default": "class extends le{constructor(){super(...arguments),this._dialogRef=null,this._contentElement=null,this.opened=!1,this.label=\"\",this.width=\"560px\",this._handleDocumentKeyDown=e=>{\"Escape\"===e.key&&this.close()}}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(\"keydown\",this._handleDocumentKeyDown)}updated(e){super.updated(e),e.has(\"opened\")&&(this.opened?document.addEventListener(\"keydown\",this._handleDocumentKeyDown):document.removeEventListener(\"keydown\",this._handleDocumentKeyDown))}open(){this.opened=!0}close(){this._dialogRef?this._dialogRef.close(void 0):(this.opened=!1,this.dispatchEvent(new CustomEvent(\"close\",{bubbles:!0,composed:!0})))}static open(e,t){document.querySelectorAll(\"kr-dialog\").forEach((e=>{e._dialogRef&&e.remove()}));const i=new Ve,s=document.createElement(\"kr-dialog\");i.setDialogElement(s),s._dialogRef=i;const o=new e;return o.dialogRef=i,t?.data&&(o.data=t.data),t?.label&&(s.label=t.label),t?.width&&(s.width=t.width),s._contentElement=o,s.opened=!0,document.body.appendChild(s),i}_handleBackdropClick(e){e.target.classList.contains(\"backdrop\")&&this.close()}render(){return V` <div class=\"backdrop\" @click=${this._handleBackdropClick}></div> <div class=\"dialog\" style=${Le({width:this.width})}> ${this.label?V`<div class=\"dialog__header\"><div class=\"dialog__header-label\">${this.label}</div></div>`:\"\"} ${this._contentElement?this._contentElement:V`<slot></slot>`} </div> `}}"
919
940
  },
920
941
  {
921
942
  "kind": "variable",
@@ -924,22 +945,22 @@
924
945
  {
925
946
  "kind": "variable",
926
947
  "name": "We",
927
- "default": "class extends ae{constructor(){super(...arguments),this.justified=!1,this.size=\"medium\"}updated(e){e.has(\"activeTabId\")&&this._updateActiveTab()}firstUpdated(){this._updateActiveTab()}_getTabs(){return Array.from(this.querySelectorAll(\"kr-tab\"))}_updateActiveTab(){const e=this._getTabs();0!==e.length&&(this.activeTabId||(this.activeTabId=e[0]?.id),e.forEach((e=>{e.active=e.id===this.activeTabId})),this.requestUpdate())}_handleTabClick(e){e.disabled||(this.activeTabId=e.id,this.dispatchEvent(new CustomEvent(\"tab-change\",{detail:{activeTabId:e.id},bubbles:!0,composed:!0})))}_handleTabDismiss(e,t){t.stopPropagation(),this.dispatchEvent(new CustomEvent(\"tab-dismiss\",{detail:{tabId:e.id},bubbles:!0,composed:!0}))}_handleKeyDown(e){const t=this._getTabs().filter((e=>!e.disabled)),i=t.findIndex((e=>e.id===this.activeTabId));let s=-1;switch(e.key){case\"ArrowLeft\":s=i>0?i-1:t.length-1;break;case\"ArrowRight\":s=i<t.length-1?i+1:0;break;case\"Home\":s=0;break;case\"End\":s=t.length-1}if(s>=0){e.preventDefault();const i=t[s];this.activeTabId=i.id,this.dispatchEvent(new CustomEvent(\"tab-change\",{detail:{activeTabId:i.id},bubbles:!0,composed:!0}));const o=this.shadowRoot?.querySelectorAll(\".label\"),r=Array.from(o||[]).find((e=>e.getAttribute(\"data-tab-id\")===i.id));r?.focus()}}_renderTabIcon(e){const t=e.getIconElement();if(!t)return B;const i=t.cloneNode(!0);return i.removeAttribute(\"slot\"),V`<span class=\"label-icon\">${i}</span>`}render(){return V` <div class=\"header\" role=\"tablist\" @keydown=${this._handleKeyDown}> ${this._getTabs().map((e=>V` <button class=${we({label:!0,\"label--active\":e.id===this.activeTabId,\"label--justified\":this.justified})} role=\"tab\" data-tab-id=${e.id} aria-selected=${e.id===this.activeTabId} aria-controls=${`panel-${e.id}`} tabindex=${e.id===this.activeTabId?0:-1} ?disabled=${e.disabled} @click=${()=>this._handleTabClick(e)} > ${this._renderTabIcon(e)} <span>${e.label}</span> ${e.badge?V`<span class=\"label-badge\" style=${Ie({backgroundColor:e.badgeBackground,color:e.badgeColor})}>${e.badge}</span>`:B} ${e.dismissible?V` <button class=\"label-dismiss\" type=\"button\" aria-label=\"Close tab\" @click=${t=>this._handleTabDismiss(e,t)} > <svg viewBox=\"0 0 20 20\" fill=\"currentColor\" width=\"12\" height=\"12\"><path fill-rule=\"evenodd\" d=\"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z\" clip-rule=\"evenodd\"/></svg> </button> `:B} </button> `))} </div> <div class=\"content\" role=\"tabpanel\" aria-labelledby=${this.activeTabId||\"\"}> <slot @slotchange=${this._updateActiveTab}></slot> </div> `}}"
948
+ "default": "class extends le{constructor(){super(...arguments),this.justified=!1,this.size=\"medium\"}updated(e){e.has(\"activeTabId\")&&this._updateActiveTab()}firstUpdated(){this._updateActiveTab()}_getTabs(){return Array.from(this.querySelectorAll(\"kr-tab\"))}_updateActiveTab(){const e=this._getTabs();0!==e.length&&(this.activeTabId||(this.activeTabId=e[0]?.id),e.forEach((e=>{e.active=e.id===this.activeTabId})),this.requestUpdate())}_handleTabClick(e){e.disabled||(this.activeTabId=e.id,this.dispatchEvent(new CustomEvent(\"tab-change\",{detail:{activeTabId:e.id},bubbles:!0,composed:!0})))}_handleTabDismiss(e,t){t.stopPropagation(),this.dispatchEvent(new CustomEvent(\"tab-dismiss\",{detail:{tabId:e.id},bubbles:!0,composed:!0}))}_handleKeyDown(e){const t=this._getTabs().filter((e=>!e.disabled)),i=t.findIndex((e=>e.id===this.activeTabId));let s=-1;switch(e.key){case\"ArrowLeft\":s=i>0?i-1:t.length-1;break;case\"ArrowRight\":s=i<t.length-1?i+1:0;break;case\"Home\":s=0;break;case\"End\":s=t.length-1}if(s>=0){e.preventDefault();const i=t[s];this.activeTabId=i.id,this.dispatchEvent(new CustomEvent(\"tab-change\",{detail:{activeTabId:i.id},bubbles:!0,composed:!0}));const o=this.shadowRoot?.querySelectorAll(\".label\"),r=Array.from(o||[]).find((e=>e.getAttribute(\"data-tab-id\")===i.id));r?.focus()}}_renderTabIcon(e){const t=e.getIconElement();if(!t)return U;const i=t.cloneNode(!0);return i.removeAttribute(\"slot\"),V`<span class=\"label-icon\">${i}</span>`}render(){return V` <div class=\"header\" role=\"tablist\" @keydown=${this._handleKeyDown}> ${this._getTabs().map((e=>V` <button class=${we({label:!0,\"label--active\":e.id===this.activeTabId,\"label--justified\":this.justified})} role=\"tab\" data-tab-id=${e.id} aria-selected=${e.id===this.activeTabId} aria-controls=${`panel-${e.id}`} tabindex=${e.id===this.activeTabId?0:-1} ?disabled=${e.disabled} @click=${()=>this._handleTabClick(e)} > ${this._renderTabIcon(e)} <span>${e.label}</span> ${e.badge?V`<span class=\"label-badge\" style=${Le({backgroundColor:e.badgeBackground,color:e.badgeColor})}>${e.badge}</span>`:U} ${e.dismissible?V` <button class=\"label-dismiss\" type=\"button\" aria-label=\"Close tab\" @click=${t=>this._handleTabDismiss(e,t)} > <svg viewBox=\"0 0 20 20\" fill=\"currentColor\" width=\"12\" height=\"12\"><path fill-rule=\"evenodd\" d=\"M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z\" clip-rule=\"evenodd\"/></svg> </button> `:U} </button> `))} </div> <div class=\"content\" role=\"tabpanel\" aria-labelledby=${this.activeTabId||\"\"}> <slot @slotchange=${this._updateActiveTab}></slot> </div> `}}"
928
949
  },
929
950
  {
930
951
  "kind": "variable",
931
952
  "name": "Ye",
932
- "default": "class extends ae{constructor(){super(...arguments),this.id=\"\",this.label=\"\",this.badge=\"\",this.badgeBackground=\"\",this.badgeColor=\"\",this.disabled=!1,this.dismissible=!1,this.active=!1}getIconElement(){return this.querySelector('[slot=\"icon\"]')}render(){return console.log(\"tab render\"),V`<slot></slot>`}}"
953
+ "default": "class extends le{constructor(){super(...arguments),this.id=\"\",this.label=\"\",this.badge=\"\",this.badgeBackground=\"\",this.badgeColor=\"\",this.disabled=!1,this.dismissible=!1,this.active=!1}getIconElement(){return this.querySelector('[slot=\"icon\"]')}render(){return console.log(\"tab render\"),V`<slot></slot>`}}"
933
954
  },
934
955
  {
935
956
  "kind": "variable",
936
957
  "name": "Qe",
937
- "default": "class extends ae{constructor(){super(),this.label=\"\",this.name=\"\",this.value=\"\",this.placeholder=\"Select an option\",this.disabled=!1,this.required=!1,this.readonly=!1,this.hint=\"\",this._isOpen=!1,this._highlightedIndex=-1,this._touched=!1,this._handleInvalid=e=>{e.preventDefault(),this._touched=!0},this._handleOutsideClick=e=>{e.composedPath().includes(this)||this._close()},this._handleKeyDown=e=>{if(!this._isOpen)return;const t=Array.from(this.querySelectorAll(\"kr-select-option\"));switch(e.key){case\"Escape\":this._close(),this._triggerElement?.focus();break;case\"ArrowDown\":if(e.preventDefault(),t.some((e=>!e.disabled))){let e=this._highlightedIndex+1;for(;e<t.length&&t[e]?.disabled;)e++;e<t.length&&(this._highlightedIndex=e)}break;case\"ArrowUp\":e.preventDefault();{let e=this._highlightedIndex-1;for(;e>=0&&t[e]?.disabled;)e--;e>=0&&(this._highlightedIndex=e)}break;case\"Enter\":e.preventDefault(),this._highlightedIndex>=0&&this._highlightedIndex<t.length&&this._selectOption(t[this._highlightedIndex])}},this._internals=this.attachInternals()}get form(){return this._internals.form}get validity(){return this._internals.validity}get validationMessage(){return this._internals.validationMessage}get willValidate(){return this._internals.willValidate}checkValidity(){return this._internals.checkValidity()}reportValidity(){return this._internals.reportValidity()}formResetCallback(){this.value=\"\",this._touched=!1,this._internals.setFormValue(\"\"),this._internals.setValidity({})}formStateRestoreCallback(e){this.value=e}connectedCallback(){super.connectedCallback(),document.addEventListener(\"click\",this._handleOutsideClick),document.addEventListener(\"keydown\",this._handleKeyDown),this.addEventListener(\"invalid\",this._handleInvalid)}firstUpdated(){this._updateValidity()}updated(e){(e.has(\"required\")||e.has(\"value\"))&&this._updateValidity()}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(\"click\",this._handleOutsideClick),document.removeEventListener(\"keydown\",this._handleKeyDown),this.removeEventListener(\"invalid\",this._handleInvalid)}_toggle(){if(!this.disabled&&!this.readonly)if(this._isOpen)this._close();else{this._isOpen=!0;const e=Array.from(this.querySelectorAll(\"kr-select-option\"));this._highlightedIndex=e.findIndex((e=>e.value===this.value)),requestAnimationFrame((()=>{const e=this.shadowRoot?.querySelector(\".select-dropdown\");if(e){const t=this._triggerElement.getBoundingClientRect(),i=window.innerHeight-t.bottom-4-8;e.style.top=t.bottom+4+\"px\",e.style.left=t.left+\"px\",e.style.width=t.width+\"px\",e.style.maxHeight=i+\"px\"}}))}}_close(){this._isOpen=!1,this._highlightedIndex=-1}_selectOption(e){e.disabled||(this.value=e.value,this._internals.setFormValue(this.value),this._updateValidity(),this.dispatchEvent(new Event(\"change\",{bubbles:!0,composed:!0})),this._close(),this._triggerElement?.focus())}_handleBlur(){this._touched=!0,this._updateValidity()}_updateValidity(){this.required&&!this.value?this._internals.setValidity({valueMissing:!0},\"Please select an option\",this._triggerElement):this._internals.setValidity({})}render(){const e=Array.from(this.querySelectorAll(\"kr-select-option\")),t=e.find((e=>e.value===this.value))?.label;return V` <div class=\"wrapper\"> ${this.label?V` <label> ${this.label} ${this.required?V`<span class=\"required\" aria-hidden=\"true\">*</span>`:\"\"} </label> `:B} <div class=\"select-wrapper\"> <button class=${we({\"select-trigger\":!0,\"select-trigger--open\":this._isOpen,\"select-trigger--invalid\":this._touched&&this.required&&!this.value})} type=\"button\" ?disabled=${this.disabled} aria-haspopup=\"listbox\" aria-expanded=${this._isOpen} @click=${this._toggle} @blur=${this._handleBlur} > <span class=${we({\"select-value\":!0,\"select-placeholder\":!t})}> ${t||this.placeholder} </span> <svg class=${we({\"chevron-icon\":!0,\"select-icon\":!0,\"select-icon--open\":this._isOpen})} viewBox=\"0 0 24 24\" fill=\"currentColor\" > <path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z\"/> </svg> </button> <div class=${we({\"select-dropdown\":!0,hidden:!this._isOpen})} role=\"listbox\"> <div class=\"select-options\"> ${0===e.length?V`<div class=\"select-empty\">No options available</div>`:e.map(((e,t)=>{const i=e.value===this.value;return V` <div class=${we({\"select-option\":!0,\"select-option--selected\":i,\"select-option--disabled\":e.disabled,\"select-option--highlighted\":t===this._highlightedIndex})} role=\"option\" aria-selected=${i} @click=${()=>this._selectOption(e)} @mouseenter=${()=>this._highlightedIndex=t} > ${e.label} </div> `}))} </div> </div> </div> ${this._touched&&this.required&&!this.value?V`<div class=\"validation-message\">Please select an option</div>`:this.hint?V`<div class=\"hint\">${this.hint}</div>`:\"\"} </div> <div class=\"options-slot\"> <slot @slotchange=${()=>this.requestUpdate()}></slot> </div> `}focus(){this._triggerElement?.focus()}blur(){this._triggerElement?.blur()}}"
958
+ "default": "class extends le{constructor(){super(),this.label=\"\",this.name=\"\",this.value=\"\",this.placeholder=\"Select an option\",this.disabled=!1,this.required=!1,this.readonly=!1,this.hint=\"\",this._isOpen=!1,this._highlightedIndex=-1,this._touched=!1,this._handleInvalid=e=>{e.preventDefault(),this._touched=!0},this._handleOutsideClick=e=>{e.composedPath().includes(this)||this._close()},this._handleKeyDown=e=>{if(!this._isOpen)return;const t=Array.from(this.querySelectorAll(\"kr-select-option\"));switch(e.key){case\"Escape\":this._close(),this._triggerElement?.focus();break;case\"ArrowDown\":if(e.preventDefault(),t.some((e=>!e.disabled))){let e=this._highlightedIndex+1;for(;e<t.length&&t[e]?.disabled;)e++;e<t.length&&(this._highlightedIndex=e)}break;case\"ArrowUp\":e.preventDefault();{let e=this._highlightedIndex-1;for(;e>=0&&t[e]?.disabled;)e--;e>=0&&(this._highlightedIndex=e)}break;case\"Enter\":e.preventDefault(),this._highlightedIndex>=0&&this._highlightedIndex<t.length&&this._selectOption(t[this._highlightedIndex])}},this._internals=this.attachInternals()}get form(){return this._internals.form}get validity(){return this._internals.validity}get validationMessage(){return this._internals.validationMessage}get willValidate(){return this._internals.willValidate}checkValidity(){return this._internals.checkValidity()}reportValidity(){return this._internals.reportValidity()}formResetCallback(){this.value=\"\",this._touched=!1,this._internals.setFormValue(\"\"),this._internals.setValidity({})}formStateRestoreCallback(e){this.value=e}connectedCallback(){super.connectedCallback(),document.addEventListener(\"click\",this._handleOutsideClick),document.addEventListener(\"keydown\",this._handleKeyDown),this.addEventListener(\"invalid\",this._handleInvalid)}firstUpdated(){this._updateValidity()}updated(e){(e.has(\"required\")||e.has(\"value\"))&&this._updateValidity()}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(\"click\",this._handleOutsideClick),document.removeEventListener(\"keydown\",this._handleKeyDown),this.removeEventListener(\"invalid\",this._handleInvalid)}_toggle(){if(!this.disabled&&!this.readonly)if(this._isOpen)this._close();else{this._isOpen=!0;const e=Array.from(this.querySelectorAll(\"kr-select-option\"));this._highlightedIndex=e.findIndex((e=>e.value===this.value)),requestAnimationFrame((()=>{const e=this.shadowRoot?.querySelector(\".select-dropdown\");if(e){const t=this._triggerElement.getBoundingClientRect(),i=window.innerHeight-t.bottom-4-8;e.style.top=t.bottom+4+\"px\",e.style.left=t.left+\"px\",e.style.width=t.width+\"px\",e.style.maxHeight=i+\"px\"}}))}}_close(){this._isOpen=!1,this._highlightedIndex=-1}_selectOption(e){e.disabled||(this.value=e.value,this._internals.setFormValue(this.value),this._updateValidity(),this.dispatchEvent(new Event(\"change\",{bubbles:!0,composed:!0})),this._close(),this._triggerElement?.focus())}_handleBlur(){this._touched=!0,this._updateValidity()}_updateValidity(){this.required&&!this.value?this._internals.setValidity({valueMissing:!0},\"Please select an option\",this._triggerElement):this._internals.setValidity({})}render(){const e=Array.from(this.querySelectorAll(\"kr-select-option\")),t=e.find((e=>e.value===this.value))?.label;return V` <div class=\"wrapper\"> ${this.label?V` <label> ${this.label} ${this.required?V`<span class=\"required\" aria-hidden=\"true\">*</span>`:\"\"} </label> `:U} <div class=\"select-wrapper\"> <button class=${we({\"select-trigger\":!0,\"select-trigger--open\":this._isOpen,\"select-trigger--invalid\":this._touched&&this.required&&!this.value})} type=\"button\" ?disabled=${this.disabled} aria-haspopup=\"listbox\" aria-expanded=${this._isOpen} @click=${this._toggle} @blur=${this._handleBlur} > <span class=${we({\"select-value\":!0,\"select-placeholder\":!t})}> ${t||this.placeholder} </span> <svg class=${we({\"chevron-icon\":!0,\"select-icon\":!0,\"select-icon--open\":this._isOpen})} viewBox=\"0 0 24 24\" fill=\"currentColor\" > <path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z\"/> </svg> </button> <div class=${we({\"select-dropdown\":!0,hidden:!this._isOpen})} role=\"listbox\"> <div class=\"select-options\"> ${0===e.length?V`<div class=\"select-empty\">No options available</div>`:e.map(((e,t)=>{const i=e.value===this.value;return V` <div class=${we({\"select-option\":!0,\"select-option--selected\":i,\"select-option--disabled\":e.disabled,\"select-option--highlighted\":t===this._highlightedIndex})} role=\"option\" aria-selected=${i} @click=${()=>this._selectOption(e)} @mouseenter=${()=>this._highlightedIndex=t} > ${e.label} </div> `}))} </div> </div> </div> ${this._touched&&this.required&&!this.value?V`<div class=\"validation-message\">Please select an option</div>`:this.hint?V`<div class=\"hint\">${this.hint}</div>`:\"\"} </div> <div class=\"options-slot\"> <slot @slotchange=${()=>this.requestUpdate()}></slot> </div> `}focus(){this._triggerElement?.focus()}blur(){this._triggerElement?.blur()}}"
938
959
  },
939
960
  {
940
961
  "kind": "variable",
941
962
  "name": "Je",
942
- "default": "class extends ae{constructor(){super(...arguments),this.value=\"\",this.disabled=!1}get label(){return this.textContent?.trim()||\"\"}render(){return V`<slot></slot>`}}"
963
+ "default": "class extends le{constructor(){super(...arguments),this.value=\"\",this.disabled=!1}get label(){return this.textContent?.trim()||\"\"}render(){return V`<slot></slot>`}}"
943
964
  },
944
965
  {
945
966
  "kind": "variable",
@@ -1151,41 +1172,46 @@
1151
1172
  {
1152
1173
  "kind": "variable",
1153
1174
  "name": "pt",
1154
- "default": "class extends ae{constructor(){super(...arguments),this._scrollStyle=\"overlay\",this._data=[],this._dataState=\"idle\",this._page=1,this._pageSize=50,this._totalItems=0,this._totalPages=0,this._searchQuery=\"\",this._canScrollLeft=!1,this._canScrollRight=!1,this._canScrollHorizontal=!1,this._columnPickerOpen=!1,this._filterPanelOpened=null,this._filterPanelTab=\"filter\",this._buckets=new Map,this._filterPanelPos={top:0,left:0},this._resizing=null,this._resizeObserver=null,this._searchPositionLocked=!1,this._model=new ht,this.def={columns:[]},this._handleClickOutside=e=>{const t=e.composedPath();if(this._columnPickerOpen){const e=this.shadowRoot?.querySelector(\".column-picker-wrapper\");e&&!t.includes(e)&&(this._columnPickerOpen=!1)}this._filterPanelOpened&&(t.some((e=>e.classList?.contains(\"filter-panel\")))||this._handleFilterApply())},this._handleResizeMove=e=>{if(!this._resizing)return;const t=this._model.columns.find((e=>e.id===this._resizing.columnId));if(t){const i=this._resizing.startWidth+(e.clientX-this._resizing.startX);t.width=`${Math.min(900,Math.max(50,i))}px`,this.requestUpdate()}},this._handleResizeEnd=()=>{this._resizing=null,document.removeEventListener(\"mousemove\",this._handleResizeMove),document.removeEventListener(\"mouseup\",this._handleResizeEnd)}}connectedCallback(){super.connectedCallback(),this.classList.toggle(\"kr-table--scroll-overlay\",\"overlay\"===this._scrollStyle),this.classList.toggle(\"kr-table--scroll-edge\",\"edge\"===this._scrollStyle),this._fetch(),this._initRefresh(),document.addEventListener(\"click\",this._handleClickOutside),this._resizeObserver=new ResizeObserver((()=>{this._searchPositionLocked=!1,this._updateSearchPosition()})),this._resizeObserver.observe(this)}disconnectedCallback(){super.disconnectedCallback(),clearInterval(this._refreshTimer),document.removeEventListener(\"click\",this._handleClickOutside),this._resizeObserver?.disconnect()}willUpdate(e){e.has(\"def\")&&(this._model=new ht,this.def.title&&(this._model.title=this.def.title),this.def.actions&&(this._model.actions=this.def.actions),this.def.data&&(this._model.data=this.def.data),this.def.dataSource&&(this._model.dataSource=this.def.dataSource),\"number\"==typeof this.def.refreshInterval&&(this._model.refreshInterval=this.def.refreshInterval),\"number\"==typeof this.def.pageSize&&(this._model.pageSize=this.def.pageSize),this.def.rowClickable&&(this._model.rowClickable=this.def.rowClickable),this.def.rowHref&&(this._model.rowHref=this.def.rowHref),this._model.columns=this.def.columns.map((e=>{const t={...e,filter:null};return t.type||(t.type=\"string\"),\"actions\"===t.type?(t.label=e.label??\"\",t.sticky=\"right\",t.resizable=!1,t):((e.filterable||e.facetable)&&(t.filter=new dt,t.filter.field=e.id,t.filter.type=t.type,e.facetable&&!e.filterable?(t.filter.operator=\"in\",t.filter.value=[]):\"string\"===t.filter.type&&(t.filter.operator=\"contains\")),t)})),this.def.displayedColumns?this._model.displayedColumns=this.def.displayedColumns:this._model.displayedColumns=this._model.columns.map((e=>e.id)),this._fetch(),this._initRefresh())}updated(e){this._updateScrollFlags(),this._syncSlottedContent()}_syncSlottedContent(){const e=this.getDisplayedColumns().filter((e=>e.render));e.length&&(this.querySelectorAll('[slot^=\"cell-\"]').forEach((e=>e.remove())),this._data.forEach(((t,i)=>{e.forEach((e=>{const s=e.render(t);if(!s)return;const o=document.createElement(\"span\");o.slot=`cell-${i}-${e.id}`,\"actions\"===e.type&&(o.style.display=\"flex\",o.style.gap=\"8px\"),\"string\"==typeof s?o.innerHTML=s:oe(s,o),this.appendChild(o)}))})))}refresh(){this._fetch()}goToPrevPage(){this._page>1&&(this._page--,this._fetch())}goToNextPage(){this._page<this._totalPages&&(this._page++,this._fetch())}goToPage(e){e>=1&&e<=this._totalPages&&(this._page=e,this._fetch())}_toSolrData(){const e={page:this._page-1,size:this._pageSize,sorts:[],filterFields:[],queryFields:[],facetFields:[]};for(const t of this._model.columns){if(!t.filter||t.filter.isEmpty()||!t.filter.isValid())continue;const i=t.filter.toSolrData();t.facetable&&\"in\"===t.filter.operator&&(i.tagged=!0),e.filterFields.push(i)}for(const t of this._model.columns)t.facetable&&e.facetFields.push({name:t.id,type:\"FIELD\",limit:100,sort:\"count\",minimumCount:1});return this._searchQuery?.trim().length&&e.queryFields.push({name:\"_text_\",operation:\"IS\",value:ot(this._searchQuery,!1)}),e}_toDbParams(){const e={page:this._page-1,size:this._pageSize,sorts:[],filterFields:[],queryFields:[],facetFields:[]};for(const t of this._model.columns)t.filter&&!t.filter.isEmpty()&&t.filter.isValid()&&e.filterFields.push(t.filter.toDbParams());return this._searchQuery?.trim().length&&this._model.columns.filter((e=>e.searchable)).forEach((t=>{e.queryFields.push({name:t.id,operation:\"CONTAINS\",value:this._searchQuery,and:!1})})),e}_fetch(){if(this._model.data)return this._data=this._model.data,this._totalItems=this._model.data.length,this._totalPages=Math.ceil(this._model.data.length/this._pageSize),void(this._dataState=\"success\");if(!this._model.dataSource)return;let e;this._dataState=\"loading\",e=\"db\"===this._model.dataSource.mode?this._toDbParams():this._toSolrData(),this._model.dataSource.fetch(e).then((e=>{switch(this._model.dataSource?.mode){case\"opensearch\":throw Error(\"Opensearch not supported yet\");case\"db\":{const t=e;this._data=t.data.content,this._totalItems=t.data.totalElements,this._totalPages=t.data.totalPages,this._pageSize=t.data.size;break}default:{const t=e;this._data=t.data.content,this._totalItems=t.data.totalElements,this._totalPages=t.data.totalPages,this._pageSize=t.data.size,this._parseFacetResults(t)}}this._dataState=\"success\",this._updateSearchPosition()})).catch((e=>{this._dataState=\"error\",Fe.show({message:e instanceof Error?e.message:\"Failed to load data\",type:\"error\"})}))}_parseFacetResults(e){if(e.data.facetFields){for(const t of this._model.columns){if(!t.facetable)continue;const i=e.data.facetFields[t.id];if(!i){this._buckets.set(t.id,[]);continue}const s=[];for(const e of i){let i=e.name;\"boolean\"===t.type&&\"string\"==typeof e.name&&(\"true\"===e.name?i=!0:\"false\"===e.name&&(i=!1)),null===e.name&&e.count>0&&s.unshift({val:null,count:e.count}),null!==e.name&&s.push({val:i,count:e.count})}if(t.filter&&\"in\"===t.filter.operator&&Array.isArray(t.filter.value))for(const e of t.filter.value)s.some((t=>t.val===e))||s.push({val:e,count:0});this._buckets.set(t.id,s)}this._buckets=new Map(this._buckets)}}_initRefresh(){clearInterval(this._refreshTimer),this._model.refreshInterval&&this._model.refreshInterval>0&&(this._refreshTimer=window.setInterval((()=>{this._fetch()}),this._model.refreshInterval))}_handleSearch(e){const t=e.target;this._searchQuery=t.value,this._page=1,this._fetch()}_getGridTemplateColumns(){return this.getDisplayedColumns().map((e=>e.width?e.width:\"actions\"===e.type?\"max-content\":\"minmax(80px, auto)\")).join(\" \")}_updateSearchPosition(){if(this._searchPositionLocked)return;const e=this.shadowRoot?.querySelector(\".search\"),t=e?.querySelector(\".search-field\");e&&t&&(e.style.justifyContent=\"center\",t.style.marginLeft=\"\",requestAnimationFrame((()=>{const i=e.getBoundingClientRect(),s=t.getBoundingClientRect().left-i.left;e.style.justifyContent=\"flex-start\",t.style.marginLeft=`${s}px`,this._searchPositionLocked=!0})))}_toggleColumnPicker(){this._columnPickerOpen=!this._columnPickerOpen}_toggleColumn(e){this._model.displayedColumns.includes(e)?this._model.displayedColumns=this._model.displayedColumns.filter((t=>t!==e)):this._model.displayedColumns=[...this._model.displayedColumns,e]}_handleRowMouseDown(){this._model.rowClickable&&window.getSelection()?.removeAllRanges()}_handleRowClick(e,t){if(!this._model.rowClickable)return;const i=window.getSelection();i&&i.toString().length>0||this.dispatchEvent(new CustomEvent(\"row-click\",{detail:{row:e,rowIndex:t},bubbles:!0,composed:!0}))}getDisplayedColumns(){return this._model.displayedColumns.map((e=>this._model.columns.find((t=>t.id===e)))).sort(((e,t)=>\"actions\"===e.type&&\"actions\"!==t.type?1:\"actions\"!==e.type&&\"actions\"===t.type?-1:0))}_handleScroll(e){const t=e.target;this._canScrollLeft=t.scrollLeft>0,this._canScrollRight=t.scrollLeft<t.scrollWidth-t.clientWidth-1}_updateScrollFlags(){const e=this.shadowRoot?.querySelector(\".content\");e&&(this._canScrollLeft=e.scrollLeft>0,this._canScrollRight=e.scrollWidth>e.clientWidth&&e.scrollLeft<e.scrollWidth-e.clientWidth-1,this._canScrollHorizontal=e.scrollWidth>e.clientWidth),this.classList.toggle(\"kr-table--scroll-left-available\",this._canScrollLeft),this.classList.toggle(\"kr-table--scroll-right-available\",this._canScrollRight),this.classList.toggle(\"kr-table--scroll-horizontal-available\",this._canScrollHorizontal),this.classList.toggle(\"kr-table--sticky-left\",this.getDisplayedColumns().some((e=>\"left\"===e.sticky))),this.classList.toggle(\"kr-table--sticky-right\",this.getDisplayedColumns().some((e=>\"right\"===e.sticky)))}_handleResizeStart(e,t){e.preventDefault();const i=this.shadowRoot?.querySelector(`.header-cell[data-column-id=\"${t}\"]`);this._resizing={columnId:t,startX:e.clientX,startWidth:i?.offsetWidth||200},document.addEventListener(\"mousemove\",this._handleResizeMove),document.addEventListener(\"mouseup\",this._handleResizeEnd)}_handleAction(e){e.href||this.dispatchEvent(new CustomEvent(\"action\",{detail:{action:e.id},bubbles:!0,composed:!0}))}_handleKqlChange(e,t){const i=e.target.value.trim();if(i){if(t.filter.setKql(i),this.requestUpdate(),!t.filter.isValid())return}else t.filter.clear(),this.requestUpdate();this._page=1,this._fetch()}_handleFilterPanelToggle(e,t){if(e.stopPropagation(),this._filterPanelOpened===t.id)this._filterPanelOpened=null;else{const i=e.currentTarget.getBoundingClientRect();this._filterPanelPos={top:i.bottom+4,left:i.left},this._filterPanelOpened=t.id,t.facetable?this._filterPanelTab=\"counts\":this._filterPanelTab=\"filter\"}}_handleKqlClear(e){e.filter.clear(),this._page=1,this._fetch()}_handleFilterClear(){const e=this._model.columns.find((e=>e.id===this._filterPanelOpened));e&&(e.filter.clear(),e.facetable&&!e.filterable&&(e.filter.operator=\"in\",e.filter.value=[])),this._filterPanelOpened=null,this._page=1,this._fetch()}_handleFilterTextKeydown(e,t){\"Enter\"===e.key&&(e.preventDefault(),this._handleFilterApply())}_handleOperatorChange(e,t){t.filter.setOperator(e.target.value),this.requestUpdate()}_handleFilterStringChange(e,t){t.filter.setValue(e.target.value),this.requestUpdate()}_handleFilterNumberChange(e,t){t.filter.setValue(Number(e.target.value)),this.requestUpdate()}_handleFilterDateChange(e,t){t.filter.setValue(new Date(e.target.value),\"day\"),this.requestUpdate()}_handleFilterBooleanChange(e,t){t.filter.setValue(\"true\"===e.target.value),this.requestUpdate()}_handleFilterDateStartChange(e,t){t.filter.setStart(new Date(e.target.value),\"day\"),this.requestUpdate()}_handleFilterDateEndChange(e,t){t.filter.setEnd(new Date(e.target.value),\"day\"),this.requestUpdate()}_handleFilterNumberStartChange(e,t){t.filter.setStart(Number(e.target.value)),this.requestUpdate()}_handleFilterNumberEndChange(e,t){t.filter.setEnd(Number(e.target.value)),this.requestUpdate()}_handleFilterListChange(e,t){const i=e.target.value.split(\",\").map((e=>e.trim())).filter((e=>\"\"!==e));\"number\"===t.type?t.filter.setValue(i.map((e=>Number(e)))):t.filter.setValue(i),this.requestUpdate()}_handleFilterApply(){this._filterPanelOpened=null,this._page=1,this._fetch()}_handleFilterPanelTabChange(e){this._filterPanelTab=e.detail.activeTabId}_handleBucketToggle(e,t,i){t.filter.toggle(i.val),this._page=1,this._fetch()}_renderCellContent(e,t,i){const s=t[e.id];if(e.render)return V`<slot name=\"cell-${i}-${e.id}\"></slot>`;if(null==s)return\"\";switch(e.type){case\"number\":return\"currency\"===e.format&&\"number\"==typeof s?s.toLocaleString(\"en-US\",{style:\"currency\",currency:\"USD\"}):String(s);case\"date\":{let e;if(s instanceof Date)e=s;else if(\"string\"==typeof s&&/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}/.test(s)){const t=s.replace(/(\\d{2}:\\d{2}:\\d{2}):(\\d+)$/,\"$1.$2\").replace(\" \",\"T\")+\"Z\";e=new Date(t)}else e=new Date(s);return e.toLocaleString(void 0,{year:\"numeric\",month:\"short\",day:\"numeric\",hour:\"numeric\",minute:\"2-digit\",timeZone:\"UTC\"})}case\"boolean\":return!0===s?\"Yes\":!1===s?\"No\":\"\";default:return String(s)}}_getHeaderCellClasses(e,t){return{\"header-cell\":!0,\"header-cell--align-center\":\"center\"===e.align,\"header-cell--align-right\":\"right\"===e.align,\"header-cell--sticky-left\":\"left\"===e.sticky,\"header-cell--sticky-left-last\":\"left\"===e.sticky&&!this.getDisplayedColumns().slice(t+1).some((e=>\"left\"===e.sticky)),\"header-cell--sticky-right\":\"right\"===e.sticky,\"header-cell--sticky-right-first\":\"right\"===e.sticky&&!this.getDisplayedColumns().slice(0,t).some((e=>\"right\"===e.sticky))}}_getCellClasses(e,t){return{cell:!0,\"cell--actions\":\"actions\"===e.type,\"cell--align-center\":\"center\"===e.align,\"cell--align-right\":\"right\"===e.align,\"cell--sticky-left\":\"left\"===e.sticky,\"cell--sticky-left-last\":\"left\"===e.sticky&&!this.getDisplayedColumns().slice(t+1).some((e=>\"left\"===e.sticky)),\"cell--sticky-right\":\"right\"===e.sticky,\"cell--sticky-right-first\":\"right\"===e.sticky&&!this.getDisplayedColumns().slice(0,t).some((e=>\"right\"===e.sticky))}}_getCellStyle(e,t){const i={};if(\"left\"===e.sticky){let e=0;for(let i=0;i<t;i++){const t=this.getDisplayedColumns()[i];\"left\"===t.sticky&&(e+=parseInt(t.width||\"0\",10))}i.left=`${e}px`}if(\"right\"===e.sticky){let e=0;for(let i=t+1;i<this.getDisplayedColumns().length;i++){const t=this.getDisplayedColumns()[i];\"right\"===t.sticky&&(e+=parseInt(t.width||\"0\",10))}i.right=`${e}px`}return i}_renderPagination(){const e=(this._page-1)*this._pageSize+1,t=Math.min(this._page*this._pageSize,this._totalItems);return V` <div class=\"pagination\"> <span class=\"pagination-icon ${1===this._page?\"pagination-icon--disabled\":\"\"}\" @click=${this.goToPrevPage} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\"/></svg> </span> <span class=\"pagination-info\">${e}-${t} of ${this._totalItems}</span> <span class=\"pagination-icon ${this._page===this._totalPages?\"pagination-icon--disabled\":\"\"}\" @click=${this.goToNextPage} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\"/></svg> </span> </div> `}_renderHeader(){return!this._model.title&&!this._model.actions?.length&&this._totalPages<=1?B:V` <div class=\"header\"> <div class=\"title\">${this._model.title??\"\"}</div> ${\"db\"!==this._model.dataSource?.mode||this._model.columns.some((e=>e.searchable))?V` <div class=\"search\"> <!-- TODO: Saved views dropdown <div class=\"views\"> <span>Default View</span> <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z\"/></svg> </div> --> <div class=\"search-field\"> <svg class=\"search-icon\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z\"/></svg> <input type=\"text\" class=\"search-input\" placeholder=\"Search...\" .value=${this._searchQuery} @input=${this._handleSearch} /> </div> </div> `:V`<div class=\"search\"></div>`} <div class=\"tools\"> ${this._renderPagination()} <span class=\"refresh\" title=\"Refresh\" @click=${()=>this.refresh()}> <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z\"/></svg> </span> <div class=\"column-picker-wrapper\"> <span class=\"header-icon\" title=\"Columns\" @click=${this._toggleColumnPicker}> <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M121-280v-400q0-33 23.5-56.5T201-760h559q33 0 56.5 23.5T840-680v400q0 33-23.5 56.5T760-200H201q-33 0-56.5-23.5T121-280Zm79 0h133v-400H200v400Zm213 0h133v-400H413v400Zm213 0h133v-400H626v400Z\"/></svg> </span> <div class=\"column-picker ${this._columnPickerOpen?\"open\":\"\"}\"> ${[...this._model.columns].filter((e=>\"actions\"!==e.type)).sort(((e,t)=>(e.label??e.id).localeCompare(t.label??t.id))).map((e=>V` <div class=\"column-picker-item\" @click=${()=>this._toggleColumn(e.id)}> <div class=\"column-picker-checkbox ${this._model.displayedColumns.includes(e.id)?\"checked\":\"\"}\"> <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/></svg> </div> <span class=\"column-picker-label\">${e.label??e.id}</span> </div> `))} </div> </div> ${1===this._model.actions?.length?V` <kr-button class=\"actions\" .href=${this._model.actions[0].href} .target=${this._model.actions[0].target} @click=${()=>this._handleAction(this._model.actions[0])} > ${this._model.actions[0].label} </kr-button> `:this._model.actions?.length?V` <kr-button class=\"actions\" .options=${this._model.actions.map((e=>({id:e.id,label:e.label})))} @option-select=${e=>this._handleAction({id:e.detail.id,label:e.detail.label})} > Actions </kr-button> `:B} </div> </div> `}_renderStatus(){return\"loading\"===this._dataState&&0===this._data.length?V`<div class=\"status\">Loading...</div>`:\"error\"===this._dataState&&0===this._data.length?V`<div class=\"status status--error\">Error loading data</div>`:0===this._data.length?V`<div class=\"status\">No data available</div>`:B}_renderFilterPanel(){if(!this._filterPanelOpened)return B;const e=this._model.columns.find((e=>e.id===this._filterPanelOpened));let t=V``;t=\"empty\"===e.filter.operator||\"n_empty\"===e.filter.operator?V` <input type=\"text\" class=\"filter-panel__input\" disabled .value=${e.filter.text} /> `:\"between\"===e.filter.operator&&\"date\"===e.type?V` <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${e.filter.value?.start??null} @change=${t=>this._handleFilterDateStartChange(t,e)} /> <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${e.filter.value?.end??null} @change=${t=>this._handleFilterDateEndChange(t,e)} /> `:\"between\"===e.filter.operator&&\"number\"===e.type?V` <input type=\"number\" class=\"filter-panel__input\" placeholder=\"Start\" .value=${e.filter.value?.start??\"\"} @input=${t=>this._handleFilterNumberStartChange(t,e)} @keydown=${t=>this._handleFilterTextKeydown(t,e)} /> <input type=\"number\" class=\"filter-panel__input\" placeholder=\"End\" .value=${e.filter.value?.end??\"\"} @input=${t=>this._handleFilterNumberEndChange(t,e)} @keydown=${t=>this._handleFilterTextKeydown(t,e)} /> `:\"in\"===e.filter.operator?V` <textarea class=\"filter-panel__textarea\" rows=\"3\" placeholder=\"Values (comma-separated)\" .value=${e.filter.text} @input=${t=>this._handleFilterListChange(t,e)} @keydown=${t=>this._handleFilterTextKeydown(t,e)} ></textarea> `:\"boolean\"===e.type?V` <kr-select-field placeholder=\"Value\" .value=${String(e.filter.value??\"\")} @change=${t=>this._handleFilterBooleanChange(t,e)} > <kr-select-option value=\"true\">Yes</kr-select-option> <kr-select-option value=\"false\">No</kr-select-option> </kr-select-field> `:\"date\"===e.type?V` <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${e.filter.value} @change=${t=>this._handleFilterDateChange(t,e)} /> `:\"number\"===e.type?V` <input type=\"number\" class=\"filter-panel__input\" placeholder=\"Value\" min=\"0\" .value=${e.filter.text} @input=${t=>this._handleFilterNumberChange(t,e)} @keydown=${t=>this._handleFilterTextKeydown(t,e)} /> `:V` <input type=\"text\" class=\"filter-panel__input\" placeholder=\"Value\" .value=${e.filter.text} @input=${t=>this._handleFilterStringChange(t,e)} @keydown=${t=>this._handleFilterTextKeydown(t,e)} /> `;const i=V` <div class=\"filter-panel__content\"> <kr-select-field .value=${e.filter.operator} @change=${t=>this._handleOperatorChange(t,e)} > ${tt(e.type).map((e=>V` <kr-select-option value=${e.key}>${e.label}</kr-select-option> `))} </kr-select-field> ${t} </div> `,s=this._buckets.get(e.id)||[];let o,r;return o=s.length?V` <div class=\"buckets\"> ${s.map((t=>{let i=\"(Empty)\";null!==t.val&&void 0!==t.val&&(i=\"boolean\"===e.type?!0===t.val||\"true\"===t.val?\"Yes\":\"No\":String(t.val));let s=B;return e.filter.has(t.val)&&(s=V` <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/> </svg> `),V` <div class=\"bucket\" @click=${i=>this._handleBucketToggle(i,e,t)} > <div class=${we({bucket__checkbox:!0,\"bucket__checkbox--checked\":e.filter.has(t.val)})}> ${s} </div> <span class=\"bucket__label\">${i}</span> <span class=\"bucket__count\">${t.count}</span> </div> `}))} </div> `:V`<div class=\"bucket-empty\">No data</div>`,r=e.facetable&&e.filterable?V` <kr-tab-group size=\"small\" active-tab-id=${this._filterPanelTab} @tab-change=${e=>this._handleFilterPanelTabChange(e)} > <kr-tab id=\"filter\" label=\"Filter\"> ${i} </kr-tab> <kr-tab id=\"counts\" label=\"Counts\"> ${o} </kr-tab> </kr-tab-group> `:e.facetable?o:i,V` <div class=\"filter-panel\" style=${Ie({top:this._filterPanelPos.top+\"px\",left:this._filterPanelPos.left+\"px\"})} > ${r} <div class=\"filter-panel__actions\"> <kr-button variant=\"outline\" color=\"secondary\" size=\"small\" @click=${this._handleFilterClear}> Clear </kr-button> <kr-button size=\"small\" @click=${this._handleFilterApply}> Apply </kr-button> </div> </div> `}_renderFilterRow(){const e=this.getDisplayedColumns();return e.some((e=>e.filterable||e.facetable))?V` <div class=\"filter-row\"> ${e.map(((t,i)=>t.filterable||t.facetable?V` <div class=${we({\"filter-cell\":!0,\"filter-cell--sticky-left\":\"left\"===t.sticky,\"filter-cell--sticky-right\":\"right\"===t.sticky,\"filter-cell--sticky-right-first\":\"right\"===t.sticky&&!e.slice(0,i).some((e=>\"right\"===e.sticky))})} style=${Ie(this._getCellStyle(t,i))} > <div class=\"filter-cell__wrapper\"> <input type=\"text\" class=${we({\"filter-cell__input\":!0,\"filter-cell__input--invalid\":!t.filter.isValid()})} .value=${t.filter.kql} @change=${e=>this._handleKqlChange(e,t)} /> ${t.filter?.kql?.length>0?V` <button class=\"filter-cell__clear\" @click=${()=>this._handleKqlClear(t)} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/> </svg> </button> `:B} <button class=${we({\"filter-cell__advanced\":!0,\"filter-cell__advanced--opened\":this._filterPanelOpened===t.id})} @click=${e=>this._handleFilterPanelToggle(e,t)} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z\"/> </svg> </button> </div> </div> `:V`<div class=${we({\"filter-cell\":!0,\"filter-cell--sticky-left\":\"left\"===t.sticky,\"filter-cell--sticky-right\":\"right\"===t.sticky,\"filter-cell--sticky-right-first\":\"right\"===t.sticky&&!e.slice(0,i).some((e=>\"right\"===e.sticky))})} style=${Ie(this._getCellStyle(t,i))} ></div>`))} </div> `:B}_renderTable(){return V` <div class=\"wrapper\"> <div class=\"overlay-left\"></div> <div class=\"overlay-right\"></div> ${this._renderStatus()} <div class=\"content\" @scroll=${this._handleScroll}> <div class=\"table\" style=\"grid-template-columns: ${this._getGridTemplateColumns()}\"> <div class=\"header-row\"> ${this.getDisplayedColumns().map(((e,t)=>V` <div class=${we(this._getHeaderCellClasses(e,t))} style=${Ie(this._getCellStyle(e,t))} data-column-id=${e.id} >${e.label??e.id}${!1!==e.resizable?V`<div class=\"header-cell__resize\" @mousedown=${t=>this._handleResizeStart(t,e.id)} ></div>`:B}</div> `))} </div> ${this._renderFilterRow()} ${this._data.map(((e,t)=>{const i=this.getDisplayedColumns().map(((i,s)=>V` <div class=${we(this._getCellClasses(i,s))} style=${Ie(this._getCellStyle(i,s))} data-column-id=${i.id} > ${this._renderCellContent(i,e,t)} </div> `));return this._model.rowHref?V` <a href=${this._model.rowHref(e)} class=${we({row:!0,\"row--clickable\":!0,\"row--link\":!0})} @mousedown=${()=>this._handleRowMouseDown()} @click=${()=>this._handleRowClick(e,t)} >${i}</a> `:V` <div class=${we({row:!0,\"row--clickable\":!!this._model.rowClickable})} @mousedown=${()=>this._handleRowMouseDown()} @click=${()=>this._handleRowClick(e,t)} >${i}</div> `}))} </div> </div> </div> `}render(){return this._model.columns.length?V` ${this._renderHeader()} ${this._renderTable()} ${this._renderFilterPanel()} `:V`<slot></slot>`}}"
1175
+ "default": "class extends le{constructor(){super(...arguments),this._scrollStyle=\"overlay\",this._data=[],this._dataState=\"idle\",this._page=1,this._pageSize=50,this._totalItems=0,this._totalPages=0,this._searchQuery=\"\",this._canScrollLeft=!1,this._canScrollRight=!1,this._canScrollHorizontal=!1,this._columnPickerOpen=!1,this._filterPanelOpened=null,this._filterPanelTab=\"filter\",this._buckets=new Map,this._filterPanelPos={top:0,left:0},this._resizing=null,this._resizeObserver=null,this._searchPositionLocked=!1,this._model=new ct,this.def={columns:[]},this._handleClickOutside=e=>{const t=e.composedPath();if(this._columnPickerOpen){const e=this.shadowRoot?.querySelector(\".column-picker-wrapper\");e&&!t.includes(e)&&(this._columnPickerOpen=!1)}this._filterPanelOpened&&(t.some((e=>e.classList?.contains(\"filter-panel\")))||this._handleFilterApply())},this._handleResizeMove=e=>{if(!this._resizing)return;const t=this._model.columns.find((e=>e.id===this._resizing.columnId));if(t){const i=this._resizing.startWidth+(e.clientX-this._resizing.startX);t.width=`${Math.min(900,Math.max(50,i))}px`,this.requestUpdate()}},this._handleResizeEnd=()=>{this._resizing=null,document.removeEventListener(\"mousemove\",this._handleResizeMove),document.removeEventListener(\"mouseup\",this._handleResizeEnd)}}connectedCallback(){super.connectedCallback(),this.classList.toggle(\"kr-table--scroll-overlay\",\"overlay\"===this._scrollStyle),this.classList.toggle(\"kr-table--scroll-edge\",\"edge\"===this._scrollStyle),this._fetch(),this._initRefresh(),document.addEventListener(\"click\",this._handleClickOutside),this._resizeObserver=new ResizeObserver((()=>{this._searchPositionLocked=!1,this._updateSearchPosition()})),this._resizeObserver.observe(this)}disconnectedCallback(){super.disconnectedCallback(),clearInterval(this._refreshTimer),document.removeEventListener(\"click\",this._handleClickOutside),this._resizeObserver?.disconnect()}willUpdate(e){e.has(\"def\")&&(this._model=new ct,this.def.title&&(this._model.title=this.def.title),this.def.actions&&(this._model.actions=this.def.actions),this.def.data&&(this._model.data=this.def.data),this.def.dataSource&&(this._model.dataSource=this.def.dataSource),\"number\"==typeof this.def.refreshInterval&&(this._model.refreshInterval=this.def.refreshInterval),\"number\"==typeof this.def.pageSize&&(this._model.pageSize=this.def.pageSize),this.def.rowClickable&&(this._model.rowClickable=this.def.rowClickable),this.def.rowHref&&(this._model.rowHref=this.def.rowHref),this._model.columns=this.def.columns.map((e=>{const t={...e,filter:null};return t.type||(t.type=\"string\"),\"actions\"===t.type?(t.label=e.label??\"\",t.sticky=\"right\",t.resizable=!1,t):((e.filterable||e.facetable)&&(t.filter=new dt,t.filter.field=e.id,t.filter.type=t.type,e.facetable&&!e.filterable?(t.filter.operator=\"in\",t.filter.value=[]):\"string\"===t.filter.type&&(t.filter.operator=\"contains\")),t)})),this.def.displayedColumns?this._model.displayedColumns=this.def.displayedColumns:this._model.displayedColumns=this._model.columns.map((e=>e.id)),this._fetch(),this._initRefresh())}updated(e){this._updateScrollFlags(),this._syncSlottedContent()}_syncSlottedContent(){const e=this.getDisplayedColumns().filter((e=>e.render));e.length&&(this.querySelectorAll('[slot^=\"cell-\"]').forEach((e=>e.remove())),this._data.forEach(((t,i)=>{e.forEach((e=>{const s=e.render(t);if(!s)return;const o=document.createElement(\"span\");o.slot=`cell-${i}-${e.id}`,\"actions\"===e.type&&(o.style.display=\"flex\",o.style.gap=\"8px\"),\"string\"==typeof s?o.innerHTML=s:oe(s,o),this.appendChild(o)}))})))}refresh(){this._fetch()}goToPrevPage(){this._page>1&&(this._page--,this._fetch())}goToNextPage(){this._page<this._totalPages&&(this._page++,this._fetch())}goToPage(e){e>=1&&e<=this._totalPages&&(this._page=e,this._fetch())}_toSolrData(){const e={page:this._page-1,size:this._pageSize,sorts:[],filterFields:[],queryFields:[],facetFields:[]};for(const t of this._model.columns){if(!t.filter||t.filter.isEmpty()||!t.filter.isValid())continue;const i=t.filter.toSolrData();t.facetable&&\"in\"===t.filter.operator&&(i.tagged=!0),e.filterFields.push(i)}for(const t of this._model.columns)t.facetable&&e.facetFields.push({name:t.id,type:\"FIELD\",limit:100,sort:\"count\",minimumCount:1});return this._searchQuery?.trim().length&&e.queryFields.push({name:\"_text_\",operation:\"IS\",value:ot(this._searchQuery,!1)}),e}_toDbParams(){const e={page:this._page-1,size:this._pageSize,sorts:[],filterFields:[],queryFields:[],facetFields:[]};for(const t of this._model.columns)t.filter&&!t.filter.isEmpty()&&t.filter.isValid()&&e.filterFields.push(t.filter.toDbParams());return this._searchQuery?.trim().length&&this._model.columns.filter((e=>e.searchable)).forEach((t=>{e.queryFields.push({name:t.id,operation:\"CONTAINS\",value:this._searchQuery,and:!1})})),e}_fetch(){if(this._model.data)return this._data=this._model.data,this._totalItems=this._model.data.length,this._totalPages=Math.ceil(this._model.data.length/this._pageSize),void(this._dataState=\"success\");if(!this._model.dataSource)return;let e;this._dataState=\"loading\",e=\"db\"===this._model.dataSource.mode?this._toDbParams():this._toSolrData(),this._model.dataSource.fetch(e).then((e=>{switch(this._model.dataSource?.mode){case\"opensearch\":throw Error(\"Opensearch not supported yet\");case\"db\":{const t=e;this._data=t.data.content,this._totalItems=t.data.totalElements,this._totalPages=t.data.totalPages,this._pageSize=t.data.size;break}default:{const t=e;this._data=t.data.content,this._totalItems=t.data.totalElements,this._totalPages=t.data.totalPages,this._pageSize=t.data.size,this._parseFacetResults(t)}}this._dataState=\"success\",this._updateSearchPosition()})).catch((e=>{this._dataState=\"error\",Fe.show({message:e instanceof Error?e.message:\"Failed to load data\",type:\"error\"})}))}_parseFacetResults(e){if(e.data.facetFields){for(const t of this._model.columns){if(!t.facetable)continue;const i=e.data.facetFields[t.id];if(!i){this._buckets.set(t.id,[]);continue}const s=[];for(const e of i){let i=e.name;\"boolean\"===t.type&&\"string\"==typeof e.name&&(\"true\"===e.name?i=!0:\"false\"===e.name&&(i=!1)),null===e.name&&e.count>0&&s.unshift({val:null,count:e.count}),null!==e.name&&s.push({val:i,count:e.count})}if(t.filter&&\"in\"===t.filter.operator&&Array.isArray(t.filter.value))for(const e of t.filter.value)s.some((t=>t.val===e))||s.push({val:e,count:0});this._buckets.set(t.id,s)}this._buckets=new Map(this._buckets)}}_initRefresh(){clearInterval(this._refreshTimer),this._model.refreshInterval&&this._model.refreshInterval>0&&(this._refreshTimer=window.setInterval((()=>{this._fetch()}),this._model.refreshInterval))}_handleSearch(e){const t=e.target;this._searchQuery=t.value,this._page=1,this._fetch()}_getGridTemplateColumns(){return this.getDisplayedColumns().map((e=>e.width?e.width:\"actions\"===e.type?\"max-content\":\"minmax(80px, auto)\")).join(\" \")}_updateSearchPosition(){if(this._searchPositionLocked)return;const e=this.shadowRoot?.querySelector(\".search\"),t=e?.querySelector(\".search-field\");e&&t&&(e.style.justifyContent=\"center\",t.style.marginLeft=\"\",requestAnimationFrame((()=>{const i=e.getBoundingClientRect(),s=t.getBoundingClientRect().left-i.left;e.style.justifyContent=\"flex-start\",t.style.marginLeft=`${s}px`,this._searchPositionLocked=!0})))}_toggleColumnPicker(){this._columnPickerOpen=!this._columnPickerOpen}_toggleColumn(e){this._model.displayedColumns.includes(e)?this._model.displayedColumns=this._model.displayedColumns.filter((t=>t!==e)):this._model.displayedColumns=[...this._model.displayedColumns,e]}_handleRowMouseDown(){this._model.rowClickable&&window.getSelection()?.removeAllRanges()}_handleRowClick(e,t){if(!this._model.rowClickable)return;const i=window.getSelection();i&&i.toString().length>0||this.dispatchEvent(new CustomEvent(\"row-click\",{detail:{row:e,rowIndex:t},bubbles:!0,composed:!0}))}getDisplayedColumns(){return this._model.displayedColumns.map((e=>this._model.columns.find((t=>t.id===e)))).sort(((e,t)=>\"actions\"===e.type&&\"actions\"!==t.type?1:\"actions\"!==e.type&&\"actions\"===t.type?-1:0))}_handleScroll(e){const t=e.target;this._canScrollLeft=t.scrollLeft>0,this._canScrollRight=t.scrollLeft<t.scrollWidth-t.clientWidth-1}_updateScrollFlags(){const e=this.shadowRoot?.querySelector(\".content\");e&&(this._canScrollLeft=e.scrollLeft>0,this._canScrollRight=e.scrollWidth>e.clientWidth&&e.scrollLeft<e.scrollWidth-e.clientWidth-1,this._canScrollHorizontal=e.scrollWidth>e.clientWidth),this.classList.toggle(\"kr-table--scroll-left-available\",this._canScrollLeft),this.classList.toggle(\"kr-table--scroll-right-available\",this._canScrollRight),this.classList.toggle(\"kr-table--scroll-horizontal-available\",this._canScrollHorizontal),this.classList.toggle(\"kr-table--sticky-left\",this.getDisplayedColumns().some((e=>\"left\"===e.sticky))),this.classList.toggle(\"kr-table--sticky-right\",this.getDisplayedColumns().some((e=>\"right\"===e.sticky)))}_handleResizeStart(e,t){e.preventDefault();const i=this.shadowRoot?.querySelector(`.header-cell[data-column-id=\"${t}\"]`);this._resizing={columnId:t,startX:e.clientX,startWidth:i?.offsetWidth||200},document.addEventListener(\"mousemove\",this._handleResizeMove),document.addEventListener(\"mouseup\",this._handleResizeEnd)}_handleAction(e){e.href||this.dispatchEvent(new CustomEvent(\"action\",{detail:{action:e.id},bubbles:!0,composed:!0}))}_handleKqlChange(e,t){const i=e.target.value.trim();if(i){if(t.filter.setKql(i),this.requestUpdate(),!t.filter.isValid())return}else t.filter.clear(),this.requestUpdate();this._page=1,this._fetch()}_handleFilterPanelToggle(e,t){if(e.stopPropagation(),this._filterPanelOpened===t.id)this._filterPanelOpened=null;else{const i=e.currentTarget.getBoundingClientRect();let s=i.left;s+328>window.innerWidth&&(s=window.innerWidth-328),this._filterPanelPos={top:i.bottom+4,left:s},this._filterPanelOpened=t.id,t.facetable?this._filterPanelTab=\"counts\":this._filterPanelTab=\"filter\"}}_handleKqlClear(e){e.filter.clear(),this._page=1,this._fetch()}_handleFilterClear(){const e=this._model.columns.find((e=>e.id===this._filterPanelOpened));e&&(e.filter.clear(),e.facetable&&!e.filterable&&(e.filter.operator=\"in\",e.filter.value=[])),this._filterPanelOpened=null,this._page=1,this._fetch()}_handleFilterTextKeydown(e,t){\"Enter\"===e.key&&(e.preventDefault(),this._handleFilterApply())}_handleOperatorChange(e,t){t.filter.setOperator(e.target.value),this.requestUpdate()}_handleFilterStringChange(e,t){t.filter.setValue(e.target.value),this.requestUpdate()}_handleFilterNumberChange(e,t){t.filter.setValue(Number(e.target.value)),this.requestUpdate()}_handleFilterDateChange(e,t){t.filter.setValue(new Date(e.target.value),\"day\"),this.requestUpdate()}_handleFilterBooleanChange(e,t){t.filter.setValue(\"true\"===e.target.value),this.requestUpdate()}_handleFilterDateStartChange(e,t){t.filter.setStart(new Date(e.target.value),\"day\"),this.requestUpdate()}_handleFilterDateEndChange(e,t){t.filter.setEnd(new Date(e.target.value),\"day\"),this.requestUpdate()}_handleFilterNumberStartChange(e,t){t.filter.setStart(Number(e.target.value)),this.requestUpdate()}_handleFilterNumberEndChange(e,t){t.filter.setEnd(Number(e.target.value)),this.requestUpdate()}_handleFilterListChange(e,t){const i=e.target.value.split(\",\").map((e=>e.trim())).filter((e=>\"\"!==e));\"number\"===t.type?t.filter.setValue(i.map((e=>Number(e)))):t.filter.setValue(i),this.requestUpdate()}_handleFilterApply(){this._filterPanelOpened=null,this._page=1,this._fetch()}_handleFilterPanelTabChange(e){this._filterPanelTab=e.detail.activeTabId}_handleBucketToggle(e,t,i){t.filter.toggle(i.val),this._page=1,this._fetch()}_renderCellContent(e,t,i){const s=t[e.id];if(e.render)return V`<slot name=\"cell-${i}-${e.id}\"></slot>`;if(null==s)return\"\";switch(e.type){case\"number\":return\"currency\"===e.format&&\"number\"==typeof s?s.toLocaleString(\"en-US\",{style:\"currency\",currency:\"USD\"}):String(s);case\"date\":{let e;if(s instanceof Date)e=s;else if(\"string\"==typeof s&&/^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}/.test(s)){const t=s.replace(/(\\d{2}:\\d{2}:\\d{2}):(\\d+)$/,\"$1.$2\").replace(\" \",\"T\")+\"Z\";e=new Date(t)}else e=new Date(s);return e.toLocaleString(void 0,{year:\"numeric\",month:\"short\",day:\"numeric\",hour:\"numeric\",minute:\"2-digit\",timeZone:\"UTC\"})}case\"boolean\":return!0===s?\"Yes\":!1===s?\"No\":\"\";default:return String(s)}}_getHeaderCellClasses(e,t){return{\"header-cell\":!0,\"header-cell--align-center\":\"center\"===e.align,\"header-cell--align-right\":\"right\"===e.align,\"header-cell--sticky-left\":\"left\"===e.sticky,\"header-cell--sticky-left-last\":\"left\"===e.sticky&&!this.getDisplayedColumns().slice(t+1).some((e=>\"left\"===e.sticky)),\"header-cell--sticky-right\":\"right\"===e.sticky,\"header-cell--sticky-right-first\":\"right\"===e.sticky&&!this.getDisplayedColumns().slice(0,t).some((e=>\"right\"===e.sticky))}}_getCellClasses(e,t){return{cell:!0,\"cell--actions\":\"actions\"===e.type,\"cell--align-center\":\"center\"===e.align,\"cell--align-right\":\"right\"===e.align,\"cell--sticky-left\":\"left\"===e.sticky,\"cell--sticky-left-last\":\"left\"===e.sticky&&!this.getDisplayedColumns().slice(t+1).some((e=>\"left\"===e.sticky)),\"cell--sticky-right\":\"right\"===e.sticky,\"cell--sticky-right-first\":\"right\"===e.sticky&&!this.getDisplayedColumns().slice(0,t).some((e=>\"right\"===e.sticky))}}_getCellStyle(e,t){const i={};if(\"left\"===e.sticky){let e=0;for(let i=0;i<t;i++){const t=this.getDisplayedColumns()[i];\"left\"===t.sticky&&(e+=parseInt(t.width||\"0\",10))}i.left=`${e}px`}if(\"right\"===e.sticky){let e=0;for(let i=t+1;i<this.getDisplayedColumns().length;i++){const t=this.getDisplayedColumns()[i];\"right\"===t.sticky&&(e+=parseInt(t.width||\"0\",10))}i.right=`${e}px`}return i}_renderPagination(){const e=(this._page-1)*this._pageSize+1,t=Math.min(this._page*this._pageSize,this._totalItems);return V` <div class=\"pagination\"> <span class=\"pagination-icon ${1===this._page?\"pagination-icon--disabled\":\"\"}\" @click=${this.goToPrevPage} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\"/></svg> </span> <span class=\"pagination-info\">${e}-${t} of ${this._totalItems}</span> <span class=\"pagination-icon ${this._page===this._totalPages?\"pagination-icon--disabled\":\"\"}\" @click=${this.goToNextPage} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\"/></svg> </span> </div> `}_renderHeader(){return!this._model.title&&!this._model.actions?.length&&this._totalPages<=1?U:V` <div class=\"header\"> <div class=\"title\">${this._model.title??\"\"}</div> ${\"db\"!==this._model.dataSource?.mode||this._model.columns.some((e=>e.searchable))?V` <div class=\"search\"> <!-- TODO: Saved views dropdown <div class=\"views\"> <span>Default View</span> <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z\"/></svg> </div> --> <div class=\"search-field\"> <svg class=\"search-icon\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z\"/></svg> <input type=\"text\" class=\"search-input\" placeholder=\"Search...\" .value=${this._searchQuery} @input=${this._handleSearch} /> </div> </div> `:V`<div class=\"search\"></div>`} <div class=\"tools\"> ${this._renderPagination()} <span class=\"refresh\" title=\"Refresh\" @click=${()=>this.refresh()}> <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z\"/></svg> </span> <div class=\"column-picker-wrapper\"> <span class=\"header-icon\" title=\"Columns\" @click=${this._toggleColumnPicker}> <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M121-280v-400q0-33 23.5-56.5T201-760h559q33 0 56.5 23.5T840-680v400q0 33-23.5 56.5T760-200H201q-33 0-56.5-23.5T121-280Zm79 0h133v-400H200v400Zm213 0h133v-400H413v400Zm213 0h133v-400H626v400Z\"/></svg> </span> <div class=\"column-picker ${this._columnPickerOpen?\"open\":\"\"}\"> ${[...this._model.columns].filter((e=>\"actions\"!==e.type)).sort(((e,t)=>(e.label??e.id).localeCompare(t.label??t.id))).map((e=>V` <div class=\"column-picker-item\" @click=${()=>this._toggleColumn(e.id)}> <div class=\"column-picker-checkbox ${this._model.displayedColumns.includes(e.id)?\"checked\":\"\"}\"> <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/></svg> </div> <span class=\"column-picker-label\">${e.label??e.id}</span> </div> `))} </div> </div> ${1===this._model.actions?.length?V` <kr-button class=\"actions\" .href=${this._model.actions[0].href} .target=${this._model.actions[0].target} @click=${()=>this._handleAction(this._model.actions[0])} > ${this._model.actions[0].label} </kr-button> `:this._model.actions?.length?V` <kr-button class=\"actions\" .options=${this._model.actions.map((e=>({id:e.id,label:e.label})))} @option-select=${e=>this._handleAction({id:e.detail.id,label:e.detail.label})} > Actions </kr-button> `:U} </div> </div> `}_renderStatus(){return\"loading\"===this._dataState&&0===this._data.length?V`<div class=\"status\">Loading...</div>`:\"error\"===this._dataState&&0===this._data.length?V`<div class=\"status status--error\">Error loading data</div>`:0===this._data.length?V`<div class=\"status\">No data available</div>`:U}_renderFilterPanel(){if(!this._filterPanelOpened)return U;const e=this._model.columns.find((e=>e.id===this._filterPanelOpened));let t=V``;t=\"empty\"===e.filter.operator||\"n_empty\"===e.filter.operator?V` <input type=\"text\" class=\"filter-panel__input\" disabled .value=${e.filter.text} /> `:\"between\"===e.filter.operator&&\"date\"===e.type?V` <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${e.filter.value?.start??null} @change=${t=>this._handleFilterDateStartChange(t,e)} /> <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${e.filter.value?.end??null} @change=${t=>this._handleFilterDateEndChange(t,e)} /> `:\"between\"===e.filter.operator&&\"number\"===e.type?V` <input type=\"number\" class=\"filter-panel__input\" placeholder=\"Start\" .value=${e.filter.value?.start??\"\"} @input=${t=>this._handleFilterNumberStartChange(t,e)} @keydown=${t=>this._handleFilterTextKeydown(t,e)} /> <input type=\"number\" class=\"filter-panel__input\" placeholder=\"End\" .value=${e.filter.value?.end??\"\"} @input=${t=>this._handleFilterNumberEndChange(t,e)} @keydown=${t=>this._handleFilterTextKeydown(t,e)} /> `:\"in\"===e.filter.operator?V` <textarea class=\"filter-panel__textarea\" rows=\"3\" placeholder=\"Values (comma-separated)\" .value=${e.filter.text} @input=${t=>this._handleFilterListChange(t,e)} @keydown=${t=>this._handleFilterTextKeydown(t,e)} ></textarea> `:\"boolean\"===e.type?V` <kr-select-field placeholder=\"Value\" .value=${String(e.filter.value??\"\")} @change=${t=>this._handleFilterBooleanChange(t,e)} > <kr-select-option value=\"true\">Yes</kr-select-option> <kr-select-option value=\"false\">No</kr-select-option> </kr-select-field> `:\"date\"===e.type?V` <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${e.filter.value} @change=${t=>this._handleFilterDateChange(t,e)} /> `:\"number\"===e.type?V` <input type=\"number\" class=\"filter-panel__input\" placeholder=\"Value\" min=\"0\" .value=${e.filter.text} @input=${t=>this._handleFilterNumberChange(t,e)} @keydown=${t=>this._handleFilterTextKeydown(t,e)} /> `:V` <input type=\"text\" class=\"filter-panel__input\" placeholder=\"Value\" .value=${e.filter.text} @input=${t=>this._handleFilterStringChange(t,e)} @keydown=${t=>this._handleFilterTextKeydown(t,e)} /> `;const i=V` <div class=\"filter-panel__content\"> <kr-select-field .value=${e.filter.operator} @change=${t=>this._handleOperatorChange(t,e)} > ${tt(e.type).map((e=>V` <kr-select-option value=${e.key}>${e.label}</kr-select-option> `))} </kr-select-field> ${t} </div> `,s=this._buckets.get(e.id)||[];let o,r;return o=s.length?V` <div class=\"buckets\"> ${s.map((t=>{let i=\"(Empty)\";null!==t.val&&void 0!==t.val&&(i=\"boolean\"===e.type?!0===t.val||\"true\"===t.val?\"Yes\":\"No\":String(t.val));let s=U;return e.filter.has(t.val)&&(s=V` <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/> </svg> `),V` <div class=\"bucket\" @click=${i=>this._handleBucketToggle(i,e,t)} > <div class=${we({bucket__checkbox:!0,\"bucket__checkbox--checked\":e.filter.has(t.val)})}> ${s} </div> <span class=\"bucket__label\">${i}</span> <span class=\"bucket__count\">${t.count}</span> </div> `}))} </div> `:V`<div class=\"bucket-empty\">No data</div>`,r=e.facetable&&e.filterable?V` <kr-tab-group size=\"small\" active-tab-id=${this._filterPanelTab} @tab-change=${e=>this._handleFilterPanelTabChange(e)} > <kr-tab id=\"filter\" label=\"Filter\"> ${i} </kr-tab> <kr-tab id=\"counts\" label=\"Counts\"> ${o} </kr-tab> </kr-tab-group> `:e.facetable?o:i,V` <div class=\"filter-panel\" style=${Le({top:this._filterPanelPos.top+\"px\",left:this._filterPanelPos.left+\"px\"})} > ${r} <div class=\"filter-panel__actions\"> <kr-button variant=\"outline\" color=\"secondary\" size=\"small\" @click=${this._handleFilterClear}> Clear </kr-button> <kr-button size=\"small\" @click=${this._handleFilterApply}> Apply </kr-button> </div> </div> `}_renderFilterRow(){const e=this.getDisplayedColumns();return e.some((e=>e.filterable||e.facetable))?V` <div class=\"filter-row\"> ${e.map(((t,i)=>t.filterable||t.facetable?V` <div class=${we({\"filter-cell\":!0,\"filter-cell--sticky-left\":\"left\"===t.sticky,\"filter-cell--sticky-right\":\"right\"===t.sticky,\"filter-cell--sticky-right-first\":\"right\"===t.sticky&&!e.slice(0,i).some((e=>\"right\"===e.sticky))})} style=${Le(this._getCellStyle(t,i))} > <div class=\"filter-cell__wrapper\"> <input type=\"text\" class=${we({\"filter-cell__input\":!0,\"filter-cell__input--invalid\":!t.filter.isValid()})} .value=${t.filter.kql} @change=${e=>this._handleKqlChange(e,t)} /> ${t.filter?.kql?.length>0?V` <button class=\"filter-cell__clear\" @click=${()=>this._handleKqlClear(t)} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/> </svg> </button> `:U} <button class=${we({\"filter-cell__advanced\":!0,\"filter-cell__advanced--opened\":this._filterPanelOpened===t.id})} @click=${e=>this._handleFilterPanelToggle(e,t)} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z\"/> </svg> </button> </div> </div> `:V`<div class=${we({\"filter-cell\":!0,\"filter-cell--sticky-left\":\"left\"===t.sticky,\"filter-cell--sticky-right\":\"right\"===t.sticky,\"filter-cell--sticky-right-first\":\"right\"===t.sticky&&!e.slice(0,i).some((e=>\"right\"===e.sticky))})} style=${Le(this._getCellStyle(t,i))} ></div>`))} </div> `:U}_renderTable(){return V` <div class=\"wrapper\"> <div class=\"overlay-left\"></div> <div class=\"overlay-right\"></div> ${this._renderStatus()} <div class=\"content\" @scroll=${this._handleScroll}> <div class=\"table\" style=\"grid-template-columns: ${this._getGridTemplateColumns()}\"> <div class=\"header-row\"> ${this.getDisplayedColumns().map(((e,t)=>V` <div class=${we(this._getHeaderCellClasses(e,t))} style=${Le(this._getCellStyle(e,t))} data-column-id=${e.id} >${e.label??e.id}${!1!==e.resizable?V`<div class=\"header-cell__resize\" @mousedown=${t=>this._handleResizeStart(t,e.id)} ></div>`:U}</div> `))} </div> ${this._renderFilterRow()} ${this._data.map(((e,t)=>{const i=this.getDisplayedColumns().map(((i,s)=>V` <div class=${we(this._getCellClasses(i,s))} style=${Le(this._getCellStyle(i,s))} data-column-id=${i.id} > ${this._renderCellContent(i,e,t)} </div> `));return this._model.rowHref?V` <a href=${this._model.rowHref(e)} class=${we({row:!0,\"row--clickable\":!0,\"row--link\":!0})} @mousedown=${()=>this._handleRowMouseDown()} @click=${()=>this._handleRowClick(e,t)} >${i}</a> `:V` <div class=${we({row:!0,\"row--clickable\":!!this._model.rowClickable})} @mousedown=${()=>this._handleRowMouseDown()} @click=${()=>this._handleRowClick(e,t)} >${i}</div> `}))} </div> </div> </div> `}render(){return this._model.columns.length?V` ${this._renderHeader()} ${this._renderTable()} ${this._renderFilterPanel()} `:V`<slot></slot>`}}"
1155
1176
  },
1156
1177
  {
1157
1178
  "kind": "variable",
1158
1179
  "name": "ft",
1159
- "default": "class extends ae{constructor(){super(...arguments),this.size=\"md\",this.color=\"dark\"}render(){var e=\"\",t=\"\";return e=\"sm\"==this.size?\"16px\":\"md\"==this.size?\"24px\":\"lg\"==this.size?\"32px\":\"xl\"==this.size?\"48px\":this.size,t=\"dark\"==this.color?\"#163052\":\"light\"==this.color?\"#ffffff\":this.color,V` <svg class=\"spinner\" style=${`width: ${e}; height: ${e}; color: ${t}`} viewBox=\"0 0 44 44\" role=\"status\" aria-label=\"Loading\" > <circle class=\"circle\" cx=\"22\" cy=\"22\" r=\"20\" fill=\"none\" stroke-width=\"4\" /> </svg> `}}"
1180
+ "default": "class extends le{constructor(){super(...arguments),this.size=\"md\",this.color=\"dark\"}render(){var e=\"\",t=\"\";return e=\"sm\"==this.size?\"16px\":\"md\"==this.size?\"24px\":\"lg\"==this.size?\"32px\":\"xl\"==this.size?\"48px\":this.size,t=\"dark\"==this.color?\"#163052\":\"light\"==this.color?\"#ffffff\":this.color,V` <svg class=\"spinner\" style=${`width: ${e}; height: ${e}; color: ${t}`} viewBox=\"0 0 44 44\" role=\"status\" aria-label=\"Loading\" > <circle class=\"circle\" cx=\"22\" cy=\"22\" r=\"20\" fill=\"none\" stroke-width=\"4\" /> </svg> `}}"
1160
1181
  },
1161
1182
  {
1162
1183
  "kind": "variable",
1163
1184
  "name": "vt",
1164
- "default": "class extends ae{constructor(){super(...arguments),this.color=\"dark\"}render(){let e=\"\",t=\"\";return e=\"dark\"===this.color?\"#163052\":\"light\"===this.color?\"#ffffff\":this.color,t=this.trackColor?this.trackColor:\"light\"===this.color?\"#ffffff4d\":\"#0000001a\",V` <div class=\"progress-bar\" style=${`background: ${t}`} role=\"status\" aria-label=\"Loading\" > <div class=\"fill\" style=${`background: ${e}`}></div> </div> `}}"
1185
+ "default": "class extends le{constructor(){super(...arguments),this.color=\"dark\"}render(){let e=\"\",t=\"\";return e=\"dark\"===this.color?\"#163052\":\"light\"===this.color?\"#ffffff\":this.color,t=this.trackColor?this.trackColor:\"light\"===this.color?\"#ffffff4d\":\"#0000001a\",V` <div class=\"progress-bar\" style=${`background: ${t}`} role=\"status\" aria-label=\"Loading\" > <div class=\"fill\" style=${`background: ${e}`}></div> </div> `}}"
1165
1186
  },
1166
1187
  {
1167
1188
  "kind": "variable",
1168
- "name": "_t"
1189
+ "name": "mt"
1169
1190
  },
1170
1191
  {
1171
1192
  "kind": "variable",
1172
1193
  "name": "xt",
1173
- "default": "class extends ae{constructor(){super(...arguments),this.files=[],this.emptyMessage=\"No files\"}_handleFileClick(e){if(this.dispatchEvent(new CustomEvent(\"file-click\",{bubbles:!0,composed:!0,detail:{file:e}})),e.url){_t.open({src:e.url,name:e.name}).addEventListener(\"download\",(()=>{this._handleDownload(e)}))}}_handleDownload(e){this.dispatchEvent(new CustomEvent(\"download\",{bubbles:!0,composed:!0,detail:{file:e}}))}_handleDelete(e){this.dispatchEvent(new CustomEvent(\"delete\",{bubbles:!0,composed:!0,detail:{file:e}}))}_getExtension(e){return e.split(\".\").pop()?.toLowerCase()||\"\"}_getExtClass(e){return[\"pdf\"].includes(e)?\"file-list__ext--pdf\":[\"doc\",\"docx\",\"rtf\",\"txt\"].includes(e)?\"file-list__ext--doc\":[\"xls\",\"xlsx\",\"csv\"].includes(e)?\"file-list__ext--xls\":[\"zip\",\"rar\",\"7z\",\"gz\",\"tar\"].includes(e)?\"file-list__ext--zip\":[\"jpg\",\"jpeg\",\"png\",\"gif\",\"webp\",\"svg\",\"bmp\"].includes(e)?\"file-list__ext--img\":\"file-list__ext--default\"}_getExtIcon(e){return[\"jpg\",\"jpeg\",\"png\",\"gif\",\"webp\",\"svg\",\"bmp\"].includes(e)?V`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4.86 8.86-3 3.87L9 13.14 6 17h12l-3.86-5.14z\"/></svg>`:[\"pdf\"].includes(e)?V`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M20 2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8.5 7.5c0 .83-.67 1.5-1.5 1.5H9v2H7.5V7H10c.83 0 1.5.67 1.5 1.5v1zm5 2c0 .83-.67 1.5-1.5 1.5h-2.5V7H15c.83 0 1.5.67 1.5 1.5v3zm4-3H19v1h1.5V11H19v2h-1.5V7h3v1.5zM9 9.5h1v-1H9v1zM4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm10 5.5h1v-3h-1v3z\"/></svg>`:[\"doc\",\"docx\",\"rtf\",\"txt\"].includes(e)?V`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6zm2-6h8v2H8v-2zm0-4h8v2H8v-2zm0 8h5v2H8v-2z\"/></svg>`:[\"xls\",\"xlsx\",\"csv\"].includes(e)?V`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 2v3H5V5h14zm0 5v4H5v-4h14zM5 19v-3h14v3H5zm2-8h4v2H7v-2zm0 5h4v2H7v-2z\"/></svg>`:[\"zip\",\"rar\",\"7z\",\"gz\",\"tar\"].includes(e)?V`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V6h5.17l2 2H20v10zm-8-4h2v2h-2v-2zm0-4h2v2h-2v-2zm-2 2h2v2h-2v-2z\"/></svg>`:V`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z\"/></svg>`}render(){return this.files.length?V` <div class=\"file-list\"> ${this.files.map((e=>{const t=this._getExtension(e.name),i=e.meta||e.date||\"\";return V` <div class=\"file-list__item\"> <div class=\"file-list__ext ${this._getExtClass(t)}\">${this._getExtIcon(t)}</div> <div class=\"file-list__info\"> <a class=\"file-list__name\" @click=${()=>this._handleFileClick(e)}>${e.name}</a> ${i?V`<div class=\"file-list__meta\">${i}</div>`:\"\"} </div> <div class=\"file-list__actions\"> <svg class=\"file-list__action\" @click=${()=>this._handleDownload(e)} xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" title=\"Download\"><path d=\"M19 9h-4V3H9v6H5l7 7 7-7zm-8 2V5h2v6h1.17L12 13.17 9.83 11H11zm-6 7h14v2H5v-2z\"/></svg> <svg class=\"file-list__action file-list__action--delete\" @click=${()=>this._handleDelete(e)} xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" title=\"Delete\"><path d=\"M16 9v10H8V9h8m-1.5-6h-5l-1 1H5v2h14V4h-3.5l-1-1zM18 7H6v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7z\"/></svg> </div> </div> `}))} </div> `:V`<div class=\"file-list__empty\">${this.emptyMessage}</div>`}}"
1194
+ "default": "class extends le{constructor(){super(...arguments),this.files=[],this.emptyMessage=\"No files\"}_handleFileClick(e){if(this.dispatchEvent(new CustomEvent(\"file-click\",{bubbles:!0,composed:!0,detail:{file:e}})),e.url){mt.open({src:e.url,name:e.name}).addEventListener(\"download\",(()=>{this._handleDownload(e)}))}}_handleDownload(e){this.dispatchEvent(new CustomEvent(\"download\",{bubbles:!0,composed:!0,detail:{file:e}}))}_handleDelete(e){this.dispatchEvent(new CustomEvent(\"delete\",{bubbles:!0,composed:!0,detail:{file:e}}))}_getExtension(e){return e.split(\".\").pop()?.toLowerCase()||\"\"}_getExtClass(e){return[\"pdf\"].includes(e)?\"file-list__ext--pdf\":[\"doc\",\"docx\",\"rtf\",\"txt\"].includes(e)?\"file-list__ext--doc\":[\"xls\",\"xlsx\",\"csv\"].includes(e)?\"file-list__ext--xls\":[\"zip\",\"rar\",\"7z\",\"gz\",\"tar\"].includes(e)?\"file-list__ext--zip\":[\"jpg\",\"jpeg\",\"png\",\"gif\",\"webp\",\"svg\",\"bmp\"].includes(e)?\"file-list__ext--img\":\"file-list__ext--default\"}_getExtIcon(e){return[\"jpg\",\"jpeg\",\"png\",\"gif\",\"webp\",\"svg\",\"bmp\"].includes(e)?V`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4.86 8.86-3 3.87L9 13.14 6 17h12l-3.86-5.14z\"/></svg>`:[\"pdf\"].includes(e)?V`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M20 2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-8.5 7.5c0 .83-.67 1.5-1.5 1.5H9v2H7.5V7H10c.83 0 1.5.67 1.5 1.5v1zm5 2c0 .83-.67 1.5-1.5 1.5h-2.5V7H15c.83 0 1.5.67 1.5 1.5v3zm4-3H19v1h1.5V11H19v2h-1.5V7h3v1.5zM9 9.5h1v-1H9v1zM4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm10 5.5h1v-3h-1v3z\"/></svg>`:[\"doc\",\"docx\",\"rtf\",\"txt\"].includes(e)?V`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6zm2-6h8v2H8v-2zm0-4h8v2H8v-2zm0 8h5v2H8v-2z\"/></svg>`:[\"xls\",\"xlsx\",\"csv\"].includes(e)?V`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 2v3H5V5h14zm0 5v4H5v-4h14zM5 19v-3h14v3H5zm2-8h4v2H7v-2zm0 5h4v2H7v-2z\"/></svg>`:[\"zip\",\"rar\",\"7z\",\"gz\",\"tar\"].includes(e)?V`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V6h5.17l2 2H20v10zm-8-4h2v2h-2v-2zm0-4h2v2h-2v-2zm-2 2h2v2h-2v-2z\"/></svg>`:V`<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z\"/></svg>`}render(){return this.files.length?V` <div class=\"file-list\"> ${this.files.map((e=>{const t=this._getExtension(e.name),i=e.meta||e.date||\"\";return V` <div class=\"file-list__item\"> <div class=\"file-list__ext ${this._getExtClass(t)}\">${this._getExtIcon(t)}</div> <div class=\"file-list__info\"> <a class=\"file-list__name\" @click=${()=>this._handleFileClick(e)}>${e.name}</a> ${i?V`<div class=\"file-list__meta\">${i}</div>`:\"\"} </div> <div class=\"file-list__actions\"> <svg class=\"file-list__action\" @click=${()=>this._handleDownload(e)} xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" title=\"Download\"><path d=\"M19 9h-4V3H9v6H5l7 7 7-7zm-8 2V5h2v6h1.17L12 13.17 9.83 11H11zm-6 7h14v2H5v-2z\"/></svg> <svg class=\"file-list__action file-list__action--delete\" @click=${()=>this._handleDelete(e)} xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" title=\"Delete\"><path d=\"M16 9v10H8V9h8m-1.5-6h-5l-1 1H5v2h14V4h-3.5l-1-1zM18 7H6v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7z\"/></svg> </div> </div> `}))} </div> `:V`<div class=\"file-list__empty\">${this.emptyMessage}</div>`}}"
1174
1195
  },
1175
1196
  {
1176
1197
  "kind": "variable",
1177
1198
  "name": "St",
1178
- "default": "class extends ae{constructor(){super(),this.label=\"\",this.name=\"\",this.value=\"\",this.placeholder=\"\",this.type=\"text\",this.required=!1,this.disabled=!1,this.readonly=!1,this.autocomplete=\"\",this.hint=\"\",this._touched=!1,this._dirty=!1,this._handleInvalid=e=>{e.preventDefault(),this._touched=!0},this._internals=this.attachInternals()}get form(){return this._internals.form}get validity(){return this._internals.validity}get validationMessage(){return this._internals.validationMessage}get willValidate(){return this._internals.willValidate}checkValidity(){return this._internals.checkValidity()}reportValidity(){return this._internals.reportValidity()}formResetCallback(){this.value=\"\",this._touched=!1,this._dirty=!1,this._internals.setFormValue(\"\"),this._internals.setValidity({})}formStateRestoreCallback(e){this.value=e}connectedCallback(){super.connectedCallback(),this.addEventListener(\"invalid\",this._handleInvalid)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(\"invalid\",this._handleInvalid)}firstUpdated(){this._updateValidity()}updated(e){(e.has(\"required\")||e.has(\"value\"))&&this._updateValidity()}_updateValidity(){this._input&&this._internals.setValidity(this._input.validity,this._input.validationMessage)}_handleInput(e){this.value=e.target.value,this._dirty=!0,this._internals.setFormValue(this.value),this._internals.setValidity(this._input.validity,this._input.validationMessage)}_handleChange(e){this.value=e.target.value,this._internals.setFormValue(this.value)}_handleBlur(){this._touched=!0,this._internals.setValidity(this._input.validity,this._input.validationMessage)}render(){let e=\"\";return this._touched&&this._input&&!this._input.validity.valid&&(e=this._input.validationMessage),V` <div class=\"wrapper\"> ${this.label?V` <label for=\"input\"> ${this.label} ${this.required?V`<span class=\"required\" aria-hidden=\"true\">*</span>`:\"\"} </label> `:\"\"} <input id=\"input\" class=${we({\"input--invalid\":this._touched&&this._input&&!this._input.validity.valid})} type=${this.type} name=${this.name} .value=${$t(this.value)} placeholder=${this.placeholder} ?required=${this.required} ?disabled=${this.disabled} ?readonly=${this.readonly} minlength=${kt(this.minlength)} maxlength=${kt(this.maxlength)} pattern=${kt(this.pattern)} autocomplete=${kt(this.autocomplete||void 0)} @input=${this._handleInput} @change=${this._handleChange} @blur=${this._handleBlur} /> ${e?V`<div class=\"validation-message\">${e}</div>`:this.hint?V`<div class=\"hint\">${this.hint}</div>`:\"\"} </div> `}focus(){this._input?.focus()}blur(){this._input?.blur()}select(){this._input?.select()}}"
1199
+ "default": "class extends le{constructor(){super(),this.label=\"\",this.name=\"\",this.value=\"\",this.placeholder=\"\",this.type=\"text\",this.required=!1,this.disabled=!1,this.readonly=!1,this.autocomplete=\"\",this.hint=\"\",this._touched=!1,this._dirty=!1,this._handleInvalid=e=>{e.preventDefault(),this._touched=!0},this._internals=this.attachInternals()}get form(){return this._internals.form}get validity(){return this._internals.validity}get validationMessage(){return this._internals.validationMessage}get willValidate(){return this._internals.willValidate}checkValidity(){return this._internals.checkValidity()}reportValidity(){return this._internals.reportValidity()}formResetCallback(){this.value=\"\",this._touched=!1,this._dirty=!1,this._internals.setFormValue(\"\"),this._internals.setValidity({})}formStateRestoreCallback(e){this.value=e}connectedCallback(){super.connectedCallback(),this.addEventListener(\"invalid\",this._handleInvalid)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(\"invalid\",this._handleInvalid)}firstUpdated(){this._updateValidity()}updated(e){(e.has(\"required\")||e.has(\"value\"))&&this._updateValidity()}_updateValidity(){this._input&&this._internals.setValidity(this._input.validity,this._input.validationMessage)}_handleInput(e){this.value=e.target.value,this._dirty=!0,this._internals.setFormValue(this.value),this._internals.setValidity(this._input.validity,this._input.validationMessage)}_handleChange(e){this.value=e.target.value,this._internals.setFormValue(this.value)}_handleBlur(){this._touched=!0,this._internals.setValidity(this._input.validity,this._input.validationMessage)}render(){let e=\"\";return this._touched&&this._input&&!this._input.validity.valid&&(e=this._input.validationMessage),V` <div class=\"wrapper\"> ${this.label?V` <label for=\"input\"> ${this.label} ${this.required?V`<span class=\"required\" aria-hidden=\"true\">*</span>`:\"\"} </label> `:\"\"} <input id=\"input\" class=${we({\"input--invalid\":this._touched&&this._input&&!this._input.validity.valid})} type=${this.type} name=${this.name} .value=${$t(this.value)} placeholder=${this.placeholder} ?required=${this.required} ?disabled=${this.disabled} ?readonly=${this.readonly} minlength=${kt(this.minlength)} maxlength=${kt(this.maxlength)} pattern=${kt(this.pattern)} autocomplete=${kt(this.autocomplete||void 0)} @input=${this._handleInput} @change=${this._handleChange} @blur=${this._handleBlur} /> ${e?V`<div class=\"validation-message\">${e}</div>`:this.hint?V`<div class=\"hint\">${this.hint}</div>`:\"\"} </div> `}focus(){this._input?.focus()}blur(){this._input?.blur()}select(){this._input?.select()}}"
1179
1200
  },
1180
1201
  {
1181
1202
  "kind": "variable",
1182
1203
  "name": "zt",
1183
- "default": "class extends ae{constructor(){super(),this.label=\"\",this.name=\"\",this.value=\"\",this.placeholder=\"\",this.required=!1,this.disabled=!1,this.readonly=!1,this.rows=3,this.autocomplete=\"\",this.hint=\"\",this._touched=!1,this._dirty=!1,this._handleInvalid=e=>{e.preventDefault(),this._touched=!0},this._internals=this.attachInternals()}get form(){return this._internals.form}get validity(){return this._internals.validity}get validationMessage(){return this._internals.validationMessage}get willValidate(){return this._internals.willValidate}checkValidity(){return this._internals.checkValidity()}reportValidity(){return this._internals.reportValidity()}formResetCallback(){this.value=\"\",this._touched=!1,this._dirty=!1,this._internals.setFormValue(\"\"),this._internals.setValidity({})}formStateRestoreCallback(e){this.value=e}connectedCallback(){super.connectedCallback(),this.addEventListener(\"invalid\",this._handleInvalid)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(\"invalid\",this._handleInvalid)}firstUpdated(){this._updateValidity()}updated(e){(e.has(\"required\")||e.has(\"value\"))&&this._updateValidity()}_updateValidity(){this._textarea&&this._internals.setValidity(this._textarea.validity,this._textarea.validationMessage)}_handleInput(e){this.value=e.target.value,this._dirty=!0,this._internals.setFormValue(this.value),this._internals.setValidity(this._textarea.validity,this._textarea.validationMessage)}_handleChange(e){this.value=e.target.value,this._internals.setFormValue(this.value)}_handleBlur(){this._touched=!0,this._internals.setValidity(this._textarea.validity,this._textarea.validationMessage)}render(){let e=\"\";return this._touched&&this._textarea&&!this._textarea.validity.valid&&(e=this._textarea.validationMessage),V` <div class=\"wrapper\"> ${this.label?V` <label for=\"textarea\"> ${this.label} ${this.required?V`<span class=\"required\" aria-hidden=\"true\">*</span>`:B} </label> `:B} <textarea id=\"textarea\" class=${we({\"textarea--invalid\":this._touched&&this._textarea&&!this._textarea.validity.valid})} name=${this.name} .value=${$t(this.value)} placeholder=${this.placeholder} ?required=${this.required} ?disabled=${this.disabled} ?readonly=${this.readonly} rows=${this.rows} cols=${kt(this.cols)} minlength=${kt(this.minlength)} maxlength=${kt(this.maxlength)} autocomplete=${kt(this.autocomplete||void 0)} @input=${this._handleInput} @change=${this._handleChange} @blur=${this._handleBlur} ></textarea> ${e?V`<div class=\"validation-message\">${e}</div>`:this.hint?V`<div class=\"hint\">${this.hint}</div>`:B} </div> `}focus(){this._textarea?.focus()}blur(){this._textarea?.blur()}select(){this._textarea?.select()}}"
1204
+ "default": "class extends le{constructor(){super(),this.label=\"\",this.name=\"\",this.value=\"\",this.placeholder=\"\",this.required=!1,this.disabled=!1,this.readonly=!1,this.rows=3,this.autocomplete=\"\",this.hint=\"\",this._touched=!1,this._dirty=!1,this._handleInvalid=e=>{e.preventDefault(),this._touched=!0},this._internals=this.attachInternals()}get form(){return this._internals.form}get validity(){return this._internals.validity}get validationMessage(){return this._internals.validationMessage}get willValidate(){return this._internals.willValidate}checkValidity(){return this._internals.checkValidity()}reportValidity(){return this._internals.reportValidity()}formResetCallback(){this.value=\"\",this._touched=!1,this._dirty=!1,this._internals.setFormValue(\"\"),this._internals.setValidity({})}formStateRestoreCallback(e){this.value=e}connectedCallback(){super.connectedCallback(),this.addEventListener(\"invalid\",this._handleInvalid)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(\"invalid\",this._handleInvalid)}firstUpdated(){this._updateValidity()}updated(e){(e.has(\"required\")||e.has(\"value\"))&&this._updateValidity()}_updateValidity(){this._textarea&&this._internals.setValidity(this._textarea.validity,this._textarea.validationMessage)}_handleInput(e){this.value=e.target.value,this._dirty=!0,this._internals.setFormValue(this.value),this._internals.setValidity(this._textarea.validity,this._textarea.validationMessage)}_handleChange(e){this.value=e.target.value,this._internals.setFormValue(this.value)}_handleBlur(){this._touched=!0,this._internals.setValidity(this._textarea.validity,this._textarea.validationMessage)}render(){let e=\"\";return this._touched&&this._textarea&&!this._textarea.validity.valid&&(e=this._textarea.validationMessage),V` <div class=\"wrapper\"> ${this.label?V` <label for=\"textarea\"> ${this.label} ${this.required?V`<span class=\"required\" aria-hidden=\"true\">*</span>`:U} </label> `:U} <textarea id=\"textarea\" class=${we({\"textarea--invalid\":this._touched&&this._textarea&&!this._textarea.validity.valid})} name=${this.name} .value=${$t(this.value)} placeholder=${this.placeholder} ?required=${this.required} ?disabled=${this.disabled} ?readonly=${this.readonly} rows=${this.rows} cols=${kt(this.cols)} minlength=${kt(this.minlength)} maxlength=${kt(this.maxlength)} autocomplete=${kt(this.autocomplete||void 0)} @input=${this._handleInput} @change=${this._handleChange} @blur=${this._handleBlur} ></textarea> ${e?V`<div class=\"validation-message\">${e}</div>`:this.hint?V`<div class=\"hint\">${this.hint}</div>`:U} </div> `}focus(){this._textarea?.focus()}blur(){this._textarea?.blur()}select(){this._textarea?.select()}}"
1205
+ },
1206
+ {
1207
+ "kind": "variable",
1208
+ "name": "At",
1209
+ "default": "class extends le{constructor(){super(),this._requestId=0,this._handleDocumentClick=e=>{e.composedPath().includes(this)||(this._isOpen=!1)},this.label=\"\",this.name=\"\",this.value=\"\",this.placeholder=\"\",this.disabled=!1,this.readonly=!1,this.required=!1,this.hint=\"\",this.options=[],this.fetch=null,this._isOpen=!1,this._highlightedIndex=-1,this._touched=!1,this._handleInvalid=e=>{e.preventDefault(),this._touched=!0},this._internals=this.attachInternals()}connectedCallback(){super.connectedCallback(),document.addEventListener(\"click\",this._handleDocumentClick),this.addEventListener(\"invalid\",this._handleInvalid)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(\"click\",this._handleDocumentClick),this.removeEventListener(\"invalid\",this._handleInvalid)}firstUpdated(){this._updateValidity()}updated(e){(e.has(\"required\")||e.has(\"value\"))&&this._updateValidity(),e.has(\"options\")&&this._isOpen&&this._positionDropdown()}get form(){return this._internals.form}get validity(){return this._internals.validity}get validationMessage(){return this._internals.validationMessage}checkValidity(){return this._internals.checkValidity()}reportValidity(){return this._internals.reportValidity()}formResetCallback(){this.value=\"\",this._touched=!1,this._isOpen=!1,this._highlightedIndex=-1,this._internals.setFormValue(\"\"),this._internals.setValidity({})}formStateRestoreCallback(e){this.value=e}_updateValidity(){this._input&&this._internals.setValidity(this._input.validity,this._input.validationMessage)}_fetch(){if(!this.fetch)return;this._requestId++;const e=this._requestId;this.fetch(this.value).then((t=>{e===this._requestId&&(this.options=t)})).catch((e=>{console.error(\"kr-auto-suggest: fetch failed\",e)}))}_handleInput(e){this.value=e.target.value,this._isOpen=!0,this._highlightedIndex=-1,this._positionDropdown(),this._internals.setFormValue(this.value),this._internals.setValidity(this._input.validity,this._input.validationMessage),this.dispatchEvent(new Event(\"change\",{bubbles:!0,composed:!0})),this._fetch()}_positionDropdown(){requestAnimationFrame((()=>{const e=this.shadowRoot?.querySelector(\".dropdown\");if(!e)return;const t=this._input.getBoundingClientRect(),i=window.innerHeight-t.bottom-4-8,s=t.top-4-8;e.style.left=t.left+\"px\",e.style.width=t.width+\"px\",s>i?(e.style.top=\"\",e.style.bottom=window.innerHeight-t.top+4+\"px\",e.style.maxHeight=s+\"px\"):(e.style.bottom=\"\",e.style.top=t.bottom+4+\"px\",e.style.maxHeight=i+\"px\")}))}_handleFocus(){this._isOpen=!0,this._positionDropdown(),this._fetch()}_handleBlur(){this._touched=!0,this._internals.setValidity(this._input.validity,this._input.validationMessage),setTimeout((()=>{this._isOpen=!1,this._highlightedIndex=-1}),200)}_handleKeyDown(e){switch(e.key){case\"ArrowDown\":e.preventDefault(),this._isOpen=!0,this._highlightedIndex=Math.min(this._highlightedIndex+1,this.options.length-1),this._scrollToHighlighted();break;case\"ArrowUp\":e.preventDefault(),-1===this._highlightedIndex?(this._isOpen=!0,this._highlightedIndex=this.options.length-1):this._highlightedIndex=Math.max(this._highlightedIndex-1,0),this._scrollToHighlighted();break;case\"Enter\":this._highlightedIndex>=0&&this.options[this._highlightedIndex]&&(e.preventDefault(),this._selectOption(this.options[this._highlightedIndex]));break;case\"Escape\":e.preventDefault(),this._isOpen=!1,this._highlightedIndex=-1;break;case\"Tab\":this._isOpen=!1,this._highlightedIndex=-1}}_scrollToHighlighted(){this.updateComplete.then((()=>{const e=this.shadowRoot?.querySelector(\".dropdown\"),t=this.shadowRoot?.querySelector(\".option--highlighted\");if(e&&t){const i=e.getBoundingClientRect(),s=t.getBoundingClientRect();(s.bottom>i.bottom||s.top<i.top)&&t.scrollIntoView({block:\"nearest\"})}}))}_selectOption(e){e.disabled||(this.value=e.value,this._isOpen=!1,this._highlightedIndex=-1,this._internals.setFormValue(this.value),this.dispatchEvent(new CustomEvent(\"select\",{detail:{value:e.value,option:e},bubbles:!0,composed:!0})),this.dispatchEvent(new Event(\"change\",{bubbles:!0,composed:!0})))}_handleClear(){this.value=\"\",this._internals.setFormValue(this.value),this.dispatchEvent(new Event(\"change\",{bubbles:!0,composed:!0})),this._input?.focus()}_handleOptionMouseEnter(e,t){this._highlightedIndex=t}_renderOption(e,t){return V` <button class=${we({option:!0,\"option--highlighted\":t===this._highlightedIndex})} type=\"button\" role=\"option\" aria-selected=${t===this._highlightedIndex} ?disabled=${e.disabled} @click=${t=>this._selectOption(e)} @mouseenter=${e=>this._handleOptionMouseEnter(e,t)} > ${e.label||e.value} </button> `}render(){let e=U;return this._touched&&this._input&&!this._input.validity.valid?e=V`<div class=\"validation-message\">${this._input.validationMessage}</div>`:this.hint&&(e=V`<div class=\"hint\">${this.hint}</div>`),V` <div class=\"wrapper\"> ${this.label?V` <label> ${this.label} ${this.required?V`<span class=\"required\">*</span>`:U} </label> `:U} <div class=\"input-wrapper\"> <input type=\"text\" .value=${$t(this.value)} placeholder=${this.placeholder} ?disabled=${this.disabled} ?readonly=${this.readonly} ?required=${this.required} name=${this.name} autocomplete=\"off\" role=\"combobox\" aria-autocomplete=\"list\" aria-expanded=${this._isOpen} aria-controls=\"dropdown\" class=${we({\"input--invalid\":this._touched&&this._input&&!this._input.validity.valid})} @input=${this._handleInput} @blur=${this._handleBlur} @focus=${this._handleFocus} @keydown=${this._handleKeyDown} /> <div class=\"icon-wrapper\"> ${!this.value||this.disabled||this.readonly?U:V` <svg class=\"clear\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-label=\"Clear\" @click=${this._handleClear}> <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/> </svg> `} ${this.value||this.disabled||this.readonly?U:V` <svg class=\"search-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z\"/> </svg> `} </div> </div> <div id=\"dropdown\" role=\"listbox\" class=${we({dropdown:!0,\"dropdown--open\":this._isOpen&&this.options.length>0})} > ${this.options.map(((e,t)=>this._renderOption(e,t)))} </div> ${e} </div> `}}"
1184
1210
  },
1185
1211
  {
1186
1212
  "kind": "variable",
1187
- "name": "Dt",
1188
- "default": "class extends ae{constructor(){super(),this.label=\"\",this.name=\"\",this.value=\"\",this.placeholder=\"\",this.disabled=!1,this.readonly=!1,this.required=!1,this.hint=\"\",this.options=[],this.statusType=\"finished\",this.loadingText=\"Loading...\",this.errorText=\"Error loading options\",this.emptyText=\"No matches found\",this.filteringType=\"auto\",this.highlightMatches=!0,this._isOpen=!1,this._highlightedIndex=-1,this._touched=!1,this._dirty=!1,this._handleDocumentClick=this._onDocumentClick.bind(this),this._internals=this.attachInternals()}connectedCallback(){super.connectedCallback(),document.addEventListener(\"click\",this._handleDocumentClick)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(\"click\",this._handleDocumentClick)}firstUpdated(){this._updateFormValue()}get form(){return this._internals.form}get validity(){return this._internals.validity}get validationMessage(){return this._internals.validationMessage}checkValidity(){return this._internals.checkValidity()}reportValidity(){return this._internals.reportValidity()}formResetCallback(){this.value=\"\",this._touched=!1,this._dirty=!1,this._isOpen=!1,this._highlightedIndex=-1,this._updateFormValue()}formStateRestoreCallback(e){this.value=e}get _filteredOptions(){if(\"manual\"===this.filteringType||!this.value)return this.options;const e=this.value.toLowerCase(),t=[];for(const i of this.options)if(At(i)){const s=i.options.filter((t=>{const i=(t.label||t.value).toLowerCase(),s=t.filteringTags?.join(\" \").toLowerCase()||\"\";return i.includes(e)||s.includes(e)}));s.length>0&&t.push({...i,options:s})}else{const s=(i.label||i.value).toLowerCase(),o=i.filteringTags?.join(\" \").toLowerCase()||\"\";(s.includes(e)||o.includes(e))&&t.push(i)}return t}get _flatOptions(){const e=[];for(const t of this._filteredOptions)At(t)?e.push(...t.options):e.push(t);return e}_updateFormValue(){this._internals.setFormValue(this.value),this.required&&!this.value?this._internals.setValidity({valueMissing:!0},\"This field is required\",this._input):this._internals.setValidity({})}_onInput(e){const t=e.target;this.value=t.value,this._dirty=!0,this._isOpen=!0,this._highlightedIndex=-1,this._updateFormValue(),this.dispatchEvent(new CustomEvent(\"change\",{detail:{value:this.value},bubbles:!0,composed:!0})),\"manual\"===this.filteringType&&this.dispatchEvent(new CustomEvent(\"load-items\",{detail:{filteringText:this.value},bubbles:!0,composed:!0}))}_onFocus(){this._isOpen=!0,\"manual\"===this.filteringType&&this.dispatchEvent(new CustomEvent(\"load-items\",{detail:{filteringText:this.value},bubbles:!0,composed:!0}))}_onBlur(){this._touched=!0,setTimeout((()=>{this._isOpen=!1,this._highlightedIndex=-1}),200),this._updateFormValue()}_onKeyDown(e){const t=this._flatOptions;switch(e.key){case\"ArrowDown\":e.preventDefault(),this._isOpen=!0,this._highlightedIndex=Math.min(this._highlightedIndex+1,t.length-1),this._scrollToHighlighted();break;case\"ArrowUp\":e.preventDefault(),this._highlightedIndex=Math.max(this._highlightedIndex-1,-1),this._scrollToHighlighted();break;case\"Enter\":this._highlightedIndex>=0&&t[this._highlightedIndex]&&(e.preventDefault(),this._selectOption(t[this._highlightedIndex]));break;case\"Escape\":e.preventDefault(),this._isOpen=!1,this._highlightedIndex=-1;break;case\"Tab\":this._isOpen=!1,this._highlightedIndex=-1}}_scrollToHighlighted(){this.updateComplete.then((()=>{const e=this.shadowRoot?.querySelector(\".dropdown\"),t=this.shadowRoot?.querySelector(\".option.is-highlighted\");if(e&&t){const i=e.getBoundingClientRect(),s=t.getBoundingClientRect();(s.bottom>i.bottom||s.top<i.top)&&t.scrollIntoView({block:\"nearest\"})}}))}_selectOption(e){e.disabled||(this.value=e.label||e.value,this._isOpen=!1,this._highlightedIndex=-1,this._dirty=!0,this._updateFormValue(),this.dispatchEvent(new CustomEvent(\"select\",{detail:{value:e.value,selectedOption:e},bubbles:!0,composed:!0})),this.dispatchEvent(new Event(\"change\",{bubbles:!0,composed:!0})))}_handleClear(){this.value=\"\",this._updateFormValue(),this.dispatchEvent(new CustomEvent(\"change\",{detail:{value:\"\"},bubbles:!0,composed:!0})),this._input?.focus()}_onDocumentClick(e){e.composedPath().includes(this)||(this._isOpen=!1)}_highlightMatch(e){if(!this.value||\"manual\"===this.filteringType||!this.highlightMatches)return V`${e}`;const t=this.value.toLowerCase(),i=e.toLowerCase().indexOf(t);if(-1===i)return V`${e}`;const s=e.slice(0,i),o=e.slice(i,i+this.value.length),r=e.slice(i+this.value.length);return V`${s}<span class=\"highlight\">${o}</span>${r}`}_renderOption(e,t){const i=t===this._highlightedIndex;return V` <button class=${we({option:!0,\"is-highlighted\":i})} type=\"button\" role=\"option\" aria-selected=${i} ?disabled=${e.disabled} @click=${()=>this._selectOption(e)} @mouseenter=${()=>{this._highlightedIndex=t}} > <div class=\"option-content\"> <div class=\"option-label\"> ${this._highlightMatch(e.label||e.value)} ${e.labelTag?V`<span class=\"option-tag\">${e.labelTag}</span>`:B} </div> ${e.description?V`<div class=\"option-description\">${e.description}</div>`:B} ${e.tags&&e.tags.length>0?V` <div class=\"option-tags\"> ${e.tags.map((e=>V`<span class=\"option-tag\">${e}</span>`))} </div> `:B} </div> </button> `}_renderDropdownContent(){if(\"loading\"===this.statusType)return V` <div class=\"status\"> <div class=\"spinner\"></div> ${this.loadingText} </div> `;if(\"error\"===this.statusType)return V` <div class=\"status is-error\"> <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\"> <path d=\"M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z\" /> </svg> ${this.errorText} </div> `;const e=this._filteredOptions;if(0===e.length)return V`<div class=\"empty\">${this.emptyText}</div>`;let t=0;return V` ${e.map((e=>{if(At(e)){const i=e.options.map((e=>{const i=this._renderOption(e,t);return t++,i}));return V` <div class=\"group-label\">${e.label}</div> ${i} `}const i=this._renderOption(e,t);return t++,i}))} `}render(){const e=this._touched&&!this.validity.valid;return V` <div class=\"field-wrapper\"> ${this.label?V` <label> ${this.label} ${this.required?V`<span class=\"required\">*</span>`:B} </label> `:B} <div class=\"input-container\"> <div class=\"input-wrapper\"> <input type=\"text\" .value=${$t(this.value)} placeholder=${kt(this.placeholder||void 0)} ?disabled=${this.disabled} ?readonly=${this.readonly} ?required=${this.required} name=${kt(this.name||void 0)} autocomplete=\"off\" role=\"combobox\" aria-autocomplete=\"list\" aria-expanded=${this._isOpen} aria-controls=\"dropdown\" class=${we({\"input--invalid\":e})} @input=${this._onInput} @blur=${this._onBlur} @focus=${this._onFocus} @keydown=${this._onKeyDown} /> <div class=\"icon-container\"> ${!this.value||this.disabled||this.readonly?\"\":V` <button class=\"clear-button\" type=\"button\" aria-label=\"Clear\" @click=${this._handleClear} > <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\"> <path d=\"M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z\" /> </svg> </button> `} ${this.value||this.disabled||this.readonly?\"\":V` <svg class=\"search-icon\" fill=\"currentColor\" viewBox=\"0 0 16 16\"> <path d=\"M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z\" /> </svg> `} </div> </div> <div id=\"dropdown\" role=\"listbox\" class=${we({dropdown:!0,\"is-open\":this._isOpen})} > ${this._renderDropdownContent()} </div> </div> ${e?V`<div class=\"validation-message\">${this.validationMessage}</div>`:this.hint?V`<div class=\"hint\">${this.hint}</div>`:B} </div> `}}"
1213
+ "name": "It",
1214
+ "default": "class extends le{constructor(){super(),this._requestId=0,this._selectedOption=null,this._handleDocumentClick=e=>{e.composedPath().includes(this)||this._close()},this.label=\"\",this.name=\"\",this.value=\"\",this.placeholder=\"Select an option\",this.disabled=!1,this.readonly=!1,this.required=!1,this.hint=\"\",this.optionValue=\"value\",this.optionLabel=\"label\",this.options=[],this.fetch=null,this.fetchSelection=null,this._isOpen=!1,this._highlightedIndex=-1,this._touched=!1,this._searchQuery=\"\",this._handleInvalid=e=>{e.preventDefault(),this._touched=!0},this._internals=this.attachInternals()}connectedCallback(){super.connectedCallback(),document.addEventListener(\"click\",this._handleDocumentClick),this.addEventListener(\"invalid\",this._handleInvalid)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(\"click\",this._handleDocumentClick),this.removeEventListener(\"invalid\",this._handleInvalid)}firstUpdated(){this._updateValidity(),this.value&&this.fetchSelection&&this._resolveSelection()}updated(e){(e.has(\"required\")||e.has(\"value\"))&&this._updateValidity(),e.has(\"value\")&&(this.value?this._selectedOption&&this._getOptionValue(this._selectedOption)===this.value||this._resolveSelection():this._selectedOption=null),e.has(\"options\")&&this._isOpen&&this._positionDropdown()}get form(){return this._internals.form}get validity(){return this._internals.validity}get validationMessage(){return this._internals.validationMessage}checkValidity(){return this._internals.checkValidity()}reportValidity(){return this._internals.reportValidity()}formResetCallback(){this.value=\"\",this._selectedOption=null,this._touched=!1,this._isOpen=!1,this._highlightedIndex=-1,this._searchQuery=\"\",this._internals.setFormValue(\"\"),this._internals.setValidity({})}formStateRestoreCallback(e){this.value=e}focus(){this._triggerElement?.focus()}blur(){this._triggerElement?.blur()}_updateValidity(){this.required&&!this.value?this._internals.setValidity({valueMissing:!0},\"Please select an option\",this._triggerElement):this._internals.setValidity({})}_handleTriggerClick(){this.disabled||this.readonly||(this._isOpen?this._close():this._open())}_open(){this._isOpen=!0,this._searchQuery=\"\",this._highlightedIndex=-1,this._fetch(),this.updateComplete.then((()=>{this._positionDropdown(),this._searchInput&&this._searchInput.focus()}))}_close(){this._isOpen=!1,this._highlightedIndex=-1,this._searchQuery=\"\"}_positionDropdown(){requestAnimationFrame((()=>{const e=this.shadowRoot?.querySelector(\".dropdown\");if(!e)return;const t=this._triggerElement.getBoundingClientRect(),i=window.innerHeight-t.bottom-4-8,s=t.top-4-8;e.style.left=t.left+\"px\",e.style.width=t.width+\"px\",i<200&&s>i?(e.style.top=\"\",e.style.bottom=window.innerHeight-t.top+4+\"px\",e.style.maxHeight=s+\"px\"):(e.style.bottom=\"\",e.style.top=t.bottom+4+\"px\",e.style.maxHeight=i+\"px\")}))}_fetch(){if(!this.fetch)return;this._requestId++;const e=this._requestId;this.fetch(this._searchQuery).then((t=>{e===this._requestId&&(this.options=t)})).catch((e=>{console.error(\"kr-combo-box: fetch failed\",e)}))}_handleSearchInput(e){this._searchQuery=e.target.value,this._highlightedIndex=-1,this._fetch()}_handleSearchKeyDown(e){switch(e.key){case\"ArrowDown\":e.preventDefault(),this._highlightedIndex=Math.min(this._highlightedIndex+1,this.options.length-1),this._scrollToHighlighted();break;case\"ArrowUp\":e.preventDefault(),-1===this._highlightedIndex?this._highlightedIndex=this.options.length-1:this._highlightedIndex=Math.max(this._highlightedIndex-1,0),this._scrollToHighlighted();break;case\"Enter\":e.preventDefault(),this._highlightedIndex>=0&&this.options[this._highlightedIndex]&&this._selectOption(this.options[this._highlightedIndex]);break;case\"Escape\":e.preventDefault(),this._close(),this._triggerElement?.focus();break;case\"Tab\":this._close()}}_handleTriggerKeyDown(e){\"ArrowDown\"!==e.key&&\"ArrowUp\"!==e.key&&\"Enter\"!==e.key&&\" \"!==e.key||(e.preventDefault(),this._isOpen||this._open())}_handleTriggerBlur(){this._touched=!0,this._updateValidity()}_scrollToHighlighted(){this.updateComplete.then((()=>{const e=this.shadowRoot?.querySelector(\".options\"),t=this.shadowRoot?.querySelector(\".option--highlighted\");if(e&&t){const i=e.getBoundingClientRect(),s=t.getBoundingClientRect();(s.bottom>i.bottom||s.top<i.top)&&t.scrollIntoView({block:\"nearest\"})}}))}_getOptionValue(e){return\"function\"==typeof this.optionValue?this.optionValue(e):e[this.optionValue]}_getOptionLabel(e){let t;return t=\"function\"==typeof this.optionLabel?this.optionLabel(e):e[this.optionLabel],t||this._getOptionValue(e)}_resolveSelection(){if(!this.fetchSelection)return;const e=this.value;this.fetchSelection(this.value).then((t=>{this.value===e&&t&&(this._selectedOption=t,this.requestUpdate())})).catch((e=>{console.error(\"kr-combo-box: fetchSelection failed\",e)}))}_selectOption(e){e.disabled||(this.value=this._getOptionValue(e),this._selectedOption=e,this._close(),this._internals.setFormValue(this.value),this._updateValidity(),this.dispatchEvent(new CustomEvent(\"select\",{detail:{option:e},bubbles:!0,composed:!0})),this.dispatchEvent(new Event(\"change\",{bubbles:!0,composed:!0})),this._triggerElement?.focus())}_handleOptionMouseEnter(e,t){this._highlightedIndex=t}_renderOption(e,t){const i=this._getOptionValue(e);return V` <button class=${we({option:!0,\"option--highlighted\":t===this._highlightedIndex,\"option--selected\":i===this.value})} type=\"button\" role=\"option\" aria-selected=${i===this.value} ?disabled=${e.disabled} @click=${t=>this._selectOption(e)} @mouseenter=${e=>this._handleOptionMouseEnter(e,t)} > ${this._getOptionLabel(e)} </button> `}render(){let e=U;return this._touched&&this.required&&!this.value?e=V`<div class=\"validation-message\">Please select an option</div>`:this.hint&&(e=V`<div class=\"hint\">${this.hint}</div>`),V` <div class=\"wrapper\"> ${this.label?V` <label> ${this.label} ${this.required?V`<span class=\"required\">*</span>`:U} </label> `:U} <button class=${we({trigger:!0,\"trigger--open\":this._isOpen,\"trigger--invalid\":this._touched&&this.required&&!this.value})} type=\"button\" ?disabled=${this.disabled} aria-haspopup=\"listbox\" aria-expanded=${this._isOpen} @click=${this._handleTriggerClick} @keydown=${this._handleTriggerKeyDown} @blur=${this._handleTriggerBlur} > <span class=${we({trigger__value:!0,trigger__placeholder:!this._selectedOption})}> ${this._selectedOption?this._getOptionLabel(this._selectedOption):this.placeholder} </span> <svg class=${we({trigger__icon:!0,\"trigger__icon--open\":this._isOpen})} viewBox=\"0 0 24 24\" fill=\"currentColor\" > <path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z\"/> </svg> </button> <div role=\"listbox\" class=${we({dropdown:!0,\"dropdown--open\":this._isOpen})} > <div class=\"search\"> <div class=\"search__field\"> <input class=\"search__input\" type=\"text\" placeholder=\"Search...\" .value=${this._searchQuery} @input=${this._handleSearchInput} @keydown=${this._handleSearchKeyDown} /> <svg class=\"search__icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z\"/> </svg> </div> </div> <div class=\"options\"> ${0===this.options.length?V`<div class=\"empty\">No options found</div>`:this.options.map(((e,t)=>this._renderOption(e,t)))} </div> </div> ${e} </div> `}}"
1189
1215
  }
1190
1216
  ],
1191
1217
  "exports": [
@@ -1225,7 +1251,7 @@
1225
1251
  "kind": "js",
1226
1252
  "name": "KRAutoSuggest",
1227
1253
  "declaration": {
1228
- "name": "Dt",
1254
+ "name": "At",
1229
1255
  "module": "dist/krubble-components.bundled.min.js"
1230
1256
  }
1231
1257
  },
@@ -1245,6 +1271,14 @@
1245
1271
  "module": "dist/krubble-components.bundled.min.js"
1246
1272
  }
1247
1273
  },
1274
+ {
1275
+ "kind": "js",
1276
+ "name": "KRComboBox",
1277
+ "declaration": {
1278
+ "name": "It",
1279
+ "module": "dist/krubble-components.bundled.min.js"
1280
+ }
1281
+ },
1248
1282
  {
1249
1283
  "kind": "js",
1250
1284
  "name": "KRContextMenu",
@@ -1257,7 +1291,7 @@
1257
1291
  "kind": "js",
1258
1292
  "name": "KRDialog",
1259
1293
  "declaration": {
1260
- "name": "Ue",
1294
+ "name": "Be",
1261
1295
  "module": "dist/krubble-components.bundled.min.js"
1262
1296
  }
1263
1297
  },
@@ -1281,7 +1315,7 @@
1281
1315
  "kind": "js",
1282
1316
  "name": "KRFilePreview",
1283
1317
  "declaration": {
1284
- "name": "_t",
1318
+ "name": "mt",
1285
1319
  "module": "dist/krubble-components.bundled.min.js"
1286
1320
  }
1287
1321
  },
@@ -1769,6 +1803,14 @@
1769
1803
  "module": "./form/index.js"
1770
1804
  }
1771
1805
  },
1806
+ {
1807
+ "kind": "js",
1808
+ "name": "KRComboBox",
1809
+ "declaration": {
1810
+ "name": "KRComboBox",
1811
+ "module": "./form/index.js"
1812
+ }
1813
+ },
1772
1814
  {
1773
1815
  "kind": "js",
1774
1816
  "name": "ContextMenuItem",
@@ -1803,41 +1845,9 @@
1803
1845
  },
1804
1846
  {
1805
1847
  "kind": "js",
1806
- "name": "SuggestionOption",
1807
- "declaration": {
1808
- "name": "SuggestionOption",
1809
- "module": "./form/auto-suggest/auto-suggest.js"
1810
- }
1811
- },
1812
- {
1813
- "kind": "js",
1814
- "name": "SuggestionOptionGroup",
1815
- "declaration": {
1816
- "name": "SuggestionOptionGroup",
1817
- "module": "./form/auto-suggest/auto-suggest.js"
1818
- }
1819
- },
1820
- {
1821
- "kind": "js",
1822
- "name": "SuggestionOptions",
1823
- "declaration": {
1824
- "name": "SuggestionOptions",
1825
- "module": "./form/auto-suggest/auto-suggest.js"
1826
- }
1827
- },
1828
- {
1829
- "kind": "js",
1830
- "name": "AutoSuggestStatusType",
1848
+ "name": "KRAutoSuggestOption",
1831
1849
  "declaration": {
1832
- "name": "AutoSuggestStatusType",
1833
- "module": "./form/auto-suggest/auto-suggest.js"
1834
- }
1835
- },
1836
- {
1837
- "kind": "js",
1838
- "name": "AutoSuggestFilteringType",
1839
- "declaration": {
1840
- "name": "AutoSuggestFilteringType",
1850
+ "name": "KRAutoSuggestOption",
1841
1851
  "module": "./form/auto-suggest/auto-suggest.js"
1842
1852
  }
1843
1853
  }
@@ -2166,6 +2176,14 @@
2166
2176
  "name": "KRAutoSuggest",
2167
2177
  "module": "./auto-suggest/auto-suggest.js"
2168
2178
  }
2179
+ },
2180
+ {
2181
+ "kind": "js",
2182
+ "name": "KRComboBox",
2183
+ "declaration": {
2184
+ "name": "KRComboBox",
2185
+ "module": "./combo-box/combo-box.js"
2186
+ }
2169
2187
  }
2170
2188
  ]
2171
2189
  },
@@ -2564,7 +2582,7 @@
2564
2582
  {
2565
2583
  "kind": "variable",
2566
2584
  "name": "KRTable",
2567
- "default": "class KRTable extends LitElement { constructor() { super(...arguments); /** * Internal flag to switch between scroll edge modes: * - 'overlay': Fixed padding with overlay elements that hide content at edges (scrollbar at viewport edge) * - 'edge': Padding scrolls with content, allowing table to reach edges when scrolling */ this._scrollStyle = 'overlay'; this._data = []; this._dataState = 'idle'; this._page = 1; this._pageSize = 50; this._totalItems = 0; this._totalPages = 0; this._searchQuery = ''; this._canScrollLeft = false; this._canScrollRight = false; this._canScrollHorizontal = false; this._columnPickerOpen = false; this._filterPanelOpened = null; this._filterPanelTab = 'filter'; this._buckets = new Map(); this._filterPanelPos = { top: 0, left: 0 }; this._resizing = null; this._resizeObserver = null; this._searchPositionLocked = false; this._model = new KRTableModel(); this.def = { columns: [] }; this._handleClickOutside = (e) => { const path = e.composedPath(); if (this._columnPickerOpen) { const picker = this.shadowRoot?.querySelector('.column-picker-wrapper'); if (picker && !path.includes(picker)) { this._columnPickerOpen = false; } } if (this._filterPanelOpened) { if (!path.some((el) => el.classList?.contains('filter-panel'))) { this._handleFilterApply(); } } }; this._handleResizeMove = (e) => { if (!this._resizing) return; const col = this._model.columns.find(c => c.id === this._resizing.columnId); if (col) { const newWidth = this._resizing.startWidth + (e.clientX - this._resizing.startX); col.width = `${Math.min(900, Math.max(50, newWidth))}px`; this.requestUpdate(); } }; this._handleResizeEnd = () => { this._resizing = null; document.removeEventListener('mousemove', this._handleResizeMove); document.removeEventListener('mouseup', this._handleResizeEnd); }; } connectedCallback() { super.connectedCallback(); this.classList.toggle('kr-table--scroll-overlay', this._scrollStyle === 'overlay'); this.classList.toggle('kr-table--scroll-edge', this._scrollStyle === 'edge'); this._fetch(); this._initRefresh(); document.addEventListener('click', this._handleClickOutside); this._resizeObserver = new ResizeObserver(() => { // Unlock and recalculate on resize since layout changes this._searchPositionLocked = false; this._updateSearchPosition(); }); this._resizeObserver.observe(this); } disconnectedCallback() { super.disconnectedCallback(); clearInterval(this._refreshTimer); document.removeEventListener('click', this._handleClickOutside); this._resizeObserver?.disconnect(); } willUpdate(changedProperties) { if (changedProperties.has('def')) { // Build internal model from user-provided def this._model = new KRTableModel(); if (this.def.title) { this._model.title = this.def.title; } if (this.def.actions) { this._model.actions = this.def.actions; } if (this.def.data) { this._model.data = this.def.data; } if (this.def.dataSource) { this._model.dataSource = this.def.dataSource; } if (typeof this.def.refreshInterval === 'number') { this._model.refreshInterval = this.def.refreshInterval; } if (typeof this.def.pageSize === 'number') { this._model.pageSize = this.def.pageSize; } if (this.def.rowClickable) { this._model.rowClickable = this.def.rowClickable; } if (this.def.rowHref) { this._model.rowHref = this.def.rowHref; } this._model.columns = this.def.columns.map(col => { const column = { ...col, filter: null }; if (!column.type) { column.type = 'string'; } if (column.type === 'actions') { column.label = col.label ?? ''; column.sticky = 'right'; column.resizable = false; return column; } if (col.filterable || col.facetable) { column.filter = new KRQuery(); column.filter.field = col.id; column.filter.type = column.type; if (col.facetable && !col.filterable) { column.filter.operator = 'in'; column.filter.value = []; } else if (column.filter.type === 'string') { column.filter.operator = 'contains'; } } return column; }); if (this.def.displayedColumns) { this._model.displayedColumns = this.def.displayedColumns; } else { this._model.displayedColumns = this._model.columns.map(c => c.id); } this._fetch(); this._initRefresh(); } } updated(changedProperties) { this._updateScrollFlags(); this._syncSlottedContent(); } /** Syncs light DOM content for cells with custom render functions */ _syncSlottedContent() { const columns = this.getDisplayedColumns().filter(col => col.render); if (!columns.length) return; // Clear old slotted content this.querySelectorAll('[slot^=\"cell-\"]').forEach(el => el.remove()); // Create new slotted content this._data.forEach((row, rowIndex) => { columns.forEach(col => { const result = col.render(row); if (!result) return; const el = document.createElement('span'); el.slot = `cell-${rowIndex}-${col.id}`; if (col.type === 'actions') { el.style.display = 'flex'; el.style.gap = '8px'; } if (typeof result === 'string') { el.innerHTML = result; } else { render(result, el); } this.appendChild(el); }); }); } // ---------------------------------------------------------------------------- // Public Interface // ---------------------------------------------------------------------------- refresh() { this._fetch(); } goToPrevPage() { if (this._page > 1) { this._page--; this._fetch(); } } goToNextPage() { if (this._page < this._totalPages) { this._page++; this._fetch(); } } goToPage(page) { if (page >= 1 && page <= this._totalPages) { this._page = page; this._fetch(); } } // ---------------------------------------------------------------------------- // Data Fetching // ---------------------------------------------------------------------------- _toSolrData() { const request = { page: this._page - 1, size: this._pageSize, sorts: [], filterFields: [], queryFields: [], facetFields: [] }; for (const col of this._model.columns) { if (!col.filter || col.filter.isEmpty() || !col.filter.isValid()) { continue; } const filterData = col.filter.toSolrData(); if (col.facetable && col.filter.operator === 'in') { filterData.tagged = true; } request.filterFields.push(filterData); } for (const col of this._model.columns) { if (!col.facetable) { continue; } request.facetFields.push({ name: col.id, type: 'FIELD', limit: 100, sort: 'count', minimumCount: 1 }); } if (this._searchQuery?.trim().length) { request.queryFields.push({ name: '_text_', operation: 'IS', value: termify(this._searchQuery, false) }); } return request; } _toDbParams() { const request = { page: this._page - 1, size: this._pageSize, sorts: [], filterFields: [], queryFields: [], facetFields: [] }; for (const col of this._model.columns) { if (!col.filter || col.filter.isEmpty() || !col.filter.isValid()) { continue; } request.filterFields.push(col.filter.toDbParams()); } if (this._searchQuery?.trim().length) { this._model.columns.filter(col => col.searchable).forEach(col => { request.queryFields.push({ name: col.id, operation: 'CONTAINS', value: this._searchQuery, and: false }); }); } return request; } /** * Fetches data from the API and updates the table. * Shows a loading spinner while fetching, then displays rows on success * or an error snackbar on failure. * Request/response format depends on dataSource.mode (solr, opensearch, db). */ _fetch() { if (this._model.data) { this._data = this._model.data; this._totalItems = this._model.data.length; this._totalPages = Math.ceil(this._model.data.length / this._pageSize); this._dataState = 'success'; return; } if (!this._model.dataSource) return; this._dataState = 'loading'; let request; if (this._model.dataSource.mode === 'db') { request = this._toDbParams(); } else { request = this._toSolrData(); } this._model.dataSource.fetch(request) .then(response => { // Parse response based on mode switch (this._model.dataSource?.mode) { case 'opensearch': { throw Error('Opensearch not supported yet'); break; } case 'db': { const res = response; this._data = res.data.content; this._totalItems = res.data.totalElements; this._totalPages = res.data.totalPages; this._pageSize = res.data.size; break; } default: { // solr const res = response; this._data = res.data.content; this._totalItems = res.data.totalElements; this._totalPages = res.data.totalPages; this._pageSize = res.data.size; this._parseFacetResults(res); } } this._dataState = 'success'; this._updateSearchPosition(); }) .catch(err => { this._dataState = 'error'; KRSnackbar.show({ message: err instanceof Error ? err.message : 'Failed to load data', type: 'error' }); }); } _parseFacetResults(response) { if (!response.data.facetFields) { return; } for (const col of this._model.columns) { if (!col.facetable) { continue; } const rawBuckets = response.data.facetFields[col.id]; if (!rawBuckets) { this._buckets.set(col.id, []); continue; } const buckets = []; for (const raw of rawBuckets) { // Solr returns boolean facet values as strings — coerce to actual booleans // so they match the filter values stored by toggle(). let val = raw.name; if (col.type === 'boolean' && typeof raw.name === 'string') { if (raw.name === 'true') { val = true; } else if (raw.name === 'false') { val = false; } } if (raw.name === null && raw.count > 0) { buckets.unshift({ val: null, count: raw.count }); } if (raw.name !== null) { buckets.push({ val: val, count: raw.count }); } } // Bucket sync: ensure selected values appear even with 0 results if (col.filter && col.filter.operator === 'in' && Array.isArray(col.filter.value)) { for (const selectedVal of col.filter.value) { if (!buckets.some(b => b.val === selectedVal)) { buckets.push({ val: selectedVal, count: 0 }); } } } this._buckets.set(col.id, buckets); } // Trigger re-render since Map mutation doesn't trigger Lit updates this._buckets = new Map(this._buckets); } /** * Sets up auto-refresh so the table automatically fetches fresh data * at a regular interval (useful for dashboards, monitoring views). * Configured via def.refreshInterval in milliseconds. */ _initRefresh() { clearInterval(this._refreshTimer); if (this._model.refreshInterval && this._model.refreshInterval > 0) { this._refreshTimer = window.setInterval(() => { this._fetch(); }, this._model.refreshInterval); } } _handleSearch(e) { const input = e.target; this._searchQuery = input.value; this._page = 1; this._fetch(); } _getGridTemplateColumns() { const cols = this.getDisplayedColumns(); return cols.map((col) => { // If column has explicit width, use it if (col.width) { return col.width; } // Actions columns: fit content without minimum if (col.type === 'actions') { return 'max-content'; } // No width specified - use content-based sizing with minimum return 'minmax(80px, auto)'; }).join(' '); } /** * Updates search position to be centered with equal gaps from title and tools. * On first call: resets to flex centering, measures position, then locks with fixed margin. * Subsequent calls are ignored unless _searchPositionLocked is reset (e.g., on resize). */ _updateSearchPosition() { // Skip if already locked (prevents shifts on pagination changes) if (this._searchPositionLocked) return; const search = this.shadowRoot?.querySelector('.search'); const searchField = search?.querySelector('.search-field'); if (!search || !searchField) return; // Reset to flex centering search.style.justifyContent = 'center'; searchField.style.marginLeft = ''; requestAnimationFrame(() => { const searchRect = search.getBoundingClientRect(); const fieldRect = searchField.getBoundingClientRect(); // Calculate how far from the left of search container the field currently is const currentOffset = fieldRect.left - searchRect.left; // Lock position: switch to flex-start and use fixed margin search.style.justifyContent = 'flex-start'; searchField.style.marginLeft = `${currentOffset}px`; // Mark as locked so pagination changes don't shift the search this._searchPositionLocked = true; }); } // ---------------------------------------------------------------------------- // Columns // ---------------------------------------------------------------------------- _toggleColumnPicker() { this._columnPickerOpen = !this._columnPickerOpen; } _toggleColumn(columnId) { if (this._model.displayedColumns.includes(columnId)) { this._model.displayedColumns = this._model.displayedColumns.filter(id => id !== columnId); } else { this._model.displayedColumns = [...this._model.displayedColumns, columnId]; } } // Clear any existing text selection on mousedown so we only detect // selections made during this click gesture, not stale selections from elsewhere _handleRowMouseDown() { if (!this._model.rowClickable) { return; } window.getSelection()?.removeAllRanges(); } _handleRowClick(row, rowIndex) { if (!this._model.rowClickable) { return; } const selection = window.getSelection(); if (selection && selection.toString().length > 0) { return; } this.dispatchEvent(new CustomEvent('row-click', { detail: { row, rowIndex }, bubbles: true, composed: true })); } // When a user toggles a column on via the column picker, it gets appended // to _displayedColumns. By mapping over _displayedColumns (not def.columns), // the new column appears at the right edge of the table instead of jumping // back to its original position in the column definition. // Actions columns are always moved to the end. getDisplayedColumns() { return this._model.displayedColumns .map(id => this._model.columns.find(col => col.id === id)) .sort((a, b) => { if (a.type === 'actions' && b.type !== 'actions') return 1; if (a.type !== 'actions' && b.type === 'actions') return -1; return 0; }); } // ---------------------------------------------------------------------------- // Scrolling // ---------------------------------------------------------------------------- /** * Scroll event handler that updates scroll flags in real-time as user scrolls. * Updates shadow indicators to show if more content exists left/right. */ _handleScroll(e) { const container = e.target; this._canScrollLeft = container.scrollLeft > 0; this._canScrollRight = container.scrollLeft < container.scrollWidth - container.clientWidth - 1; } /** * Updates scroll state flags for the table content container. * - _canScrollLeft: true if scrolled right (can scroll back left) * - _canScrollRight: true if more content exists to the right * - _canScrollHorizontal: true if content is wider than container * These flags control scroll shadow indicators and CSS classes. */ _updateScrollFlags() { const container = this.shadowRoot?.querySelector('.content'); if (container) { this._canScrollLeft = container.scrollLeft > 0; this._canScrollRight = container.scrollWidth > container.clientWidth && container.scrollLeft < container.scrollWidth - container.clientWidth - 1; this._canScrollHorizontal = container.scrollWidth > container.clientWidth; } this.classList.toggle('kr-table--scroll-left-available', this._canScrollLeft); this.classList.toggle('kr-table--scroll-right-available', this._canScrollRight); this.classList.toggle('kr-table--scroll-horizontal-available', this._canScrollHorizontal); this.classList.toggle('kr-table--sticky-left', this.getDisplayedColumns().some(c => c.sticky === 'left')); this.classList.toggle('kr-table--sticky-right', this.getDisplayedColumns().some(c => c.sticky === 'right')); } // ---------------------------------------------------------------------------- // Column Resizing // ---------------------------------------------------------------------------- _handleResizeStart(e, columnId) { e.preventDefault(); const headerCell = this.shadowRoot?.querySelector(`.header-cell[data-column-id=\"${columnId}\"]`); this._resizing = { columnId, startX: e.clientX, startWidth: headerCell?.offsetWidth || 200 }; document.addEventListener('mousemove', this._handleResizeMove); document.addEventListener('mouseup', this._handleResizeEnd); } // ---------------------------------------------------------------------------- // Header // ---------------------------------------------------------------------------- _handleAction(action) { if (action.href) { return; } this.dispatchEvent(new CustomEvent('action', { detail: { action: action.id }, bubbles: true, composed: true })); } // ---------------------------------------------------------------------------- // Filter Handlers // ---------------------------------------------------------------------------- _handleKqlChange(e, column) { const kql = e.target.value.trim(); if (!kql) { column.filter.clear(); this.requestUpdate(); } else { column.filter.setKql(kql); this.requestUpdate(); if (!column.filter.isValid()) { return; } } this._page = 1; this._fetch(); } _handleFilterPanelToggle(e, column) { e.stopPropagation(); if (this._filterPanelOpened === column.id) { this._filterPanelOpened = null; } else { const rect = e.currentTarget.getBoundingClientRect(); this._filterPanelPos = { top: rect.bottom + 4, left: rect.left }; this._filterPanelOpened = column.id; if (column.facetable) { this._filterPanelTab = 'counts'; } else { this._filterPanelTab = 'filter'; } } } _handleKqlClear(column) { column.filter.clear(); this._page = 1; this._fetch(); } _handleFilterClear() { const column = this._model.columns.find(c => c.id === this._filterPanelOpened); if (column) { column.filter.clear(); if (column.facetable && !column.filterable) { column.filter.operator = 'in'; column.filter.value = []; } } this._filterPanelOpened = null; this._page = 1; this._fetch(); } _handleFilterTextKeydown(e, column) { if (e.key === 'Enter') { e.preventDefault(); this._handleFilterApply(); } } _handleOperatorChange(e, column) { column.filter.setOperator(e.target.value); this.requestUpdate(); } _handleFilterStringChange(e, column) { column.filter.setValue(e.target.value); this.requestUpdate(); } _handleFilterNumberChange(e, column) { column.filter.setValue(Number(e.target.value)); this.requestUpdate(); } _handleFilterDateChange(e, column) { column.filter.setValue(new Date(e.target.value), 'day'); this.requestUpdate(); } _handleFilterBooleanChange(e, column) { column.filter.setValue(e.target.value === 'true'); this.requestUpdate(); } _handleFilterDateStartChange(e, column) { column.filter.setStart(new Date(e.target.value), 'day'); this.requestUpdate(); } _handleFilterDateEndChange(e, column) { column.filter.setEnd(new Date(e.target.value), 'day'); this.requestUpdate(); } _handleFilterNumberStartChange(e, column) { column.filter.setStart(Number(e.target.value)); this.requestUpdate(); } _handleFilterNumberEndChange(e, column) { column.filter.setEnd(Number(e.target.value)); this.requestUpdate(); } _handleFilterListChange(e, column) { const items = e.target.value.split(',').map((v) => v.trim()).filter((v) => v !== ''); if (column.type === 'number') { column.filter.setValue(items.map((v) => Number(v))); } else { column.filter.setValue(items); } this.requestUpdate(); } _handleFilterApply() { this._filterPanelOpened = null; this._page = 1; this._fetch(); } _handleFilterPanelTabChange(e) { this._filterPanelTab = e.detail.activeTabId; } _handleBucketToggle(e, column, bucket) { column.filter.toggle(bucket.val); this._page = 1; this._fetch(); } // ---------------------------------------------------------------------------- // Rendering // ---------------------------------------------------------------------------- _renderCellContent(column, row, rowIndex) { const value = row[column.id]; if (column.render) { // Use slot to project content from light DOM so external styles apply return html `<slot name=\"cell-${rowIndex}-${column.id}\"></slot>`; } if (value === null || value === undefined) { return ''; } switch (column.type) { case 'number': if (column.format === 'currency' && typeof value === 'number') { return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' }); } return String(value); case 'date': { let date; if (value instanceof Date) { date = value; } else if (typeof value === 'string' && /^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}/.test(value)) { // MySQL datetime format (UTC): \"2026-01-28 01:33:44:517\" // Replace last colon before ms with dot, append Z for UTC const isoString = value.replace(/(\\d{2}:\\d{2}:\\d{2}):(\\d+)$/, '$1.$2').replace(' ', 'T') + 'Z'; date = new Date(isoString); } else { date = new Date(value); } // Show date and time for datetime values in UTC return date.toLocaleString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', timeZone: 'UTC' }); } case 'boolean': if (value === true) return 'Yes'; if (value === false) return 'No'; return ''; default: return String(value); } } /** * Returns CSS classes for a header cell based on column config. */ _getHeaderCellClasses(column, index) { return { 'header-cell': true, 'header-cell--align-center': column.align === 'center', 'header-cell--align-right': column.align === 'right', 'header-cell--sticky-left': column.sticky === 'left', 'header-cell--sticky-left-last': column.sticky === 'left' && !this.getDisplayedColumns().slice(index + 1).some(c => c.sticky === 'left'), 'header-cell--sticky-right': column.sticky === 'right', 'header-cell--sticky-right-first': column.sticky === 'right' && !this.getDisplayedColumns().slice(0, index).some(c => c.sticky === 'right') }; } /** * Returns CSS classes for a table cell based on column config: * - Alignment (center, right) * - Sticky positioning (left, right) * - Border classes for the last left-sticky or first right-sticky column */ _getCellClasses(column, index) { return { 'cell': true, 'cell--actions': column.type === 'actions', 'cell--align-center': column.align === 'center', 'cell--align-right': column.align === 'right', 'cell--sticky-left': column.sticky === 'left', 'cell--sticky-left-last': column.sticky === 'left' && !this.getDisplayedColumns().slice(index + 1).some(c => c.sticky === 'left'), 'cell--sticky-right': column.sticky === 'right', 'cell--sticky-right-first': column.sticky === 'right' && !this.getDisplayedColumns().slice(0, index).some(c => c.sticky === 'right') }; } /** * Returns inline styles for a table cell: * - Width (from column config or default 150px) * - Min-width (if specified) * - Left/right offset for sticky columns (calculated from widths of preceding sticky columns) */ _getCellStyle(column, index) { const styles = {}; if (column.sticky === 'left') { let leftOffset = 0; for (let i = 0; i < index; i++) { const col = this.getDisplayedColumns()[i]; if (col.sticky === 'left') { leftOffset += parseInt(col.width || '0', 10); } } styles.left = `${leftOffset}px`; } if (column.sticky === 'right') { let rightOffset = 0; for (let i = index + 1; i < this.getDisplayedColumns().length; i++) { const col = this.getDisplayedColumns()[i]; if (col.sticky === 'right') { rightOffset += parseInt(col.width || '0', 10); } } styles.right = `${rightOffset}px`; } return styles; } /** * Renders the pagination controls: * - Previous page arrow (disabled on first page) * - Range text showing \"1-50 of 150\" format * - Next page arrow (disabled on last page) * * Hidden when there's no data or all data fits on one page. */ _renderPagination() { const start = (this._page - 1) * this._pageSize + 1; const end = Math.min(this._page * this._pageSize, this._totalItems); return html ` <div class=\"pagination\"> <span class=\"pagination-icon ${this._page === 1 ? 'pagination-icon--disabled' : ''}\" @click=${this.goToPrevPage} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\"/></svg> </span> <span class=\"pagination-info\">${start}-${end} of ${this._totalItems}</span> <span class=\"pagination-icon ${this._page === this._totalPages ? 'pagination-icon--disabled' : ''}\" @click=${this.goToNextPage} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\"/></svg> </span> </div> `; } /** * Renders the header toolbar containing: * - Title (left) * - Search bar with view selector dropdown (center) * - Tools (right): page navigation, refresh button, column visibility picker, actions dropdown * * Hidden when there's no title, no actions, and data fits on one page. */ _renderHeader() { if (!this._model.title && !this._model.actions?.length && this._totalPages <= 1) { return nothing; } return html ` <div class=\"header\"> <div class=\"title\">${this._model.title ?? ''}</div> ${this._model.dataSource?.mode === 'db' && !this._model.columns.some(col => col.searchable) ? html `<div class=\"search\"></div>` : html ` <div class=\"search\"> <!-- TODO: Saved views dropdown <div class=\"views\"> <span>Default View</span> <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z\"/></svg> </div> --> <div class=\"search-field\"> <svg class=\"search-icon\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z\"/></svg> <input type=\"text\" class=\"search-input\" placeholder=\"Search...\" .value=${this._searchQuery} @input=${this._handleSearch} /> </div> </div> `} <div class=\"tools\"> ${this._renderPagination()} <span class=\"refresh\" title=\"Refresh\" @click=${() => this.refresh()}> <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z\"/></svg> </span> <div class=\"column-picker-wrapper\"> <span class=\"header-icon\" title=\"Columns\" @click=${this._toggleColumnPicker}> <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M121-280v-400q0-33 23.5-56.5T201-760h559q33 0 56.5 23.5T840-680v400q0 33-23.5 56.5T760-200H201q-33 0-56.5-23.5T121-280Zm79 0h133v-400H200v400Zm213 0h133v-400H413v400Zm213 0h133v-400H626v400Z\"/></svg> </span> <div class=\"column-picker ${this._columnPickerOpen ? 'open' : ''}\"> ${[...this._model.columns].filter(col => col.type !== 'actions').sort((a, b) => (a.label ?? a.id).localeCompare(b.label ?? b.id)).map(col => html ` <div class=\"column-picker-item\" @click=${() => this._toggleColumn(col.id)}> <div class=\"column-picker-checkbox ${this._model.displayedColumns.includes(col.id) ? 'checked' : ''}\"> <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/></svg> </div> <span class=\"column-picker-label\">${col.label ?? col.id}</span> </div> `)} </div> </div> ${this._model.actions?.length === 1 ? html ` <kr-button class=\"actions\" .href=${this._model.actions[0].href} .target=${this._model.actions[0].target} @click=${() => this._handleAction(this._model.actions[0])} > ${this._model.actions[0].label} </kr-button> ` : this._model.actions?.length ? html ` <kr-button class=\"actions\" .options=${this._model.actions.map(a => ({ id: a.id, label: a.label }))} @option-select=${(e) => this._handleAction({ id: e.detail.id, label: e.detail.label })} > Actions </kr-button> ` : nothing} </div> </div> `; } /** Renders status message (loading, error, empty) */ _renderStatus() { if (this._dataState === 'loading' && this._data.length === 0) { return html `<div class=\"status\">Loading...</div>`; } if (this._dataState === 'error' && this._data.length === 0) { return html `<div class=\"status status--error\">Error loading data</div>`; } if (this._data.length === 0) { return html `<div class=\"status\">No data available</div>`; } return nothing; } _renderFilterPanel() { if (!this._filterPanelOpened) { return nothing; } const column = this._model.columns.find(c => c.id === this._filterPanelOpened); // Build filter content (operator + value input) let valueInput = html ``; if (column.filter.operator === 'empty' || column.filter.operator === 'n_empty') { valueInput = html ` <input type=\"text\" class=\"filter-panel__input\" disabled .value=${column.filter.text} /> `; } else if (column.filter.operator === 'between' && column.type === 'date') { valueInput = html ` <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${column.filter.value?.start ?? null} @change=${(e) => this._handleFilterDateStartChange(e, column)} /> <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${column.filter.value?.end ?? null} @change=${(e) => this._handleFilterDateEndChange(e, column)} /> `; } else if (column.filter.operator === 'between' && column.type === 'number') { valueInput = html ` <input type=\"number\" class=\"filter-panel__input\" placeholder=\"Start\" .value=${column.filter.value?.start ?? ''} @input=${(e) => this._handleFilterNumberStartChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> <input type=\"number\" class=\"filter-panel__input\" placeholder=\"End\" .value=${column.filter.value?.end ?? ''} @input=${(e) => this._handleFilterNumberEndChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> `; } else if (column.filter.operator === 'in') { valueInput = html ` <textarea class=\"filter-panel__textarea\" rows=\"3\" placeholder=\"Values (comma-separated)\" .value=${column.filter.text} @input=${(e) => this._handleFilterListChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} ></textarea> `; } else if (column.type === 'boolean') { valueInput = html ` <kr-select-field placeholder=\"Value\" .value=${String(column.filter.value ?? '')} @change=${(e) => this._handleFilterBooleanChange(e, column)} > <kr-select-option value=\"true\">Yes</kr-select-option> <kr-select-option value=\"false\">No</kr-select-option> </kr-select-field> `; } else if (column.type === 'date') { valueInput = html ` <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${column.filter.value} @change=${(e) => this._handleFilterDateChange(e, column)} /> `; } else if (column.type === 'number') { valueInput = html ` <input type=\"number\" class=\"filter-panel__input\" placeholder=\"Value\" min=\"0\" .value=${column.filter.text} @input=${(e) => this._handleFilterNumberChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> `; } else { valueInput = html ` <input type=\"text\" class=\"filter-panel__input\" placeholder=\"Value\" .value=${column.filter.text} @input=${(e) => this._handleFilterStringChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> `; } const filterContent = html ` <div class=\"filter-panel__content\"> <kr-select-field .value=${column.filter.operator} @change=${(e) => this._handleOperatorChange(e, column)} > ${getOperatorsForType(column.type).map(op => html ` <kr-select-option value=${op.key}>${op.label}</kr-select-option> `)} </kr-select-field> ${valueInput} </div> `; // Build bucket list content const buckets = this._buckets.get(column.id) || []; let bucketContent; if (!buckets.length) { bucketContent = html `<div class=\"bucket-empty\">No data</div>`; } else { bucketContent = html ` <div class=\"buckets\"> ${buckets.map(bucket => { let bucketLabel = '(Empty)'; if (bucket.val !== null && bucket.val !== undefined) { if (column.type === 'boolean') { if (bucket.val === true || bucket.val === 'true') { bucketLabel = 'Yes'; } else { bucketLabel = 'No'; } } else { bucketLabel = String(bucket.val); } } let checkIcon = nothing; if (column.filter.has(bucket.val)) { checkIcon = html ` <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/> </svg> `; } return html ` <div class=\"bucket\" @click=${(e) => this._handleBucketToggle(e, column, bucket)} > <div class=${classMap({ 'bucket__checkbox': true, 'bucket__checkbox--checked': column.filter.has(bucket.val) })}> ${checkIcon} </div> <span class=\"bucket__label\">${bucketLabel}</span> <span class=\"bucket__count\">${bucket.count}</span> </div> `; })} </div> `; } // Build panel body — tabs if both filterable+facetable, otherwise just the relevant content let panelBody; if (column.facetable && column.filterable) { panelBody = html ` <kr-tab-group size=\"small\" active-tab-id=${this._filterPanelTab} @tab-change=${(e) => this._handleFilterPanelTabChange(e)} > <kr-tab id=\"filter\" label=\"Filter\"> ${filterContent} </kr-tab> <kr-tab id=\"counts\" label=\"Counts\"> ${bucketContent} </kr-tab> </kr-tab-group> `; } else if (column.facetable) { panelBody = bucketContent; } else { panelBody = filterContent; } return html ` <div class=\"filter-panel\" style=${styleMap({ top: this._filterPanelPos.top + 'px', left: this._filterPanelPos.left + 'px' })} > ${panelBody} <div class=\"filter-panel__actions\"> <kr-button variant=\"outline\" color=\"secondary\" size=\"small\" @click=${this._handleFilterClear}> Clear </kr-button> <kr-button size=\"small\" @click=${this._handleFilterApply}> Apply </kr-button> </div> </div> `; } /** * Renders filter row below column headers. * Only displays for columns with filterable: true. */ _renderFilterRow() { const columns = this.getDisplayedColumns(); if (!columns.some(col => col.filterable || col.facetable)) { return nothing; } return html ` <div class=\"filter-row\"> ${columns.map((col, i) => { if (!col.filterable && !col.facetable) { return html `<div class=${classMap({ 'filter-cell': true, 'filter-cell--sticky-left': col.sticky === 'left', 'filter-cell--sticky-right': col.sticky === 'right', 'filter-cell--sticky-right-first': col.sticky === 'right' && !columns.slice(0, i).some((c) => c.sticky === 'right') })} style=${styleMap(this._getCellStyle(col, i))} ></div>`; } return html ` <div class=${classMap({ 'filter-cell': true, 'filter-cell--sticky-left': col.sticky === 'left', 'filter-cell--sticky-right': col.sticky === 'right', 'filter-cell--sticky-right-first': col.sticky === 'right' && !columns.slice(0, i).some((c) => c.sticky === 'right') })} style=${styleMap(this._getCellStyle(col, i))} > <div class=\"filter-cell__wrapper\"> <input type=\"text\" class=${classMap({ 'filter-cell__input': true, 'filter-cell__input--invalid': !col.filter.isValid() })} .value=${col.filter.kql} @change=${(e) => this._handleKqlChange(e, col)} /> ${col.filter?.kql?.length > 0 ? html ` <button class=\"filter-cell__clear\" @click=${() => this._handleKqlClear(col)} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/> </svg> </button> ` : nothing} <button class=${classMap({ 'filter-cell__advanced': true, 'filter-cell__advanced--opened': this._filterPanelOpened === col.id })} @click=${(e) => this._handleFilterPanelToggle(e, col)} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z\"/> </svg> </button> </div> </div> `; })} </div> `; } /** Renders the scrollable data grid with column headers and rows. */ _renderTable() { return html ` <div class=\"wrapper\"> <div class=\"overlay-left\"></div> <div class=\"overlay-right\"></div> ${this._renderStatus()} <div class=\"content\" @scroll=${this._handleScroll}> <div class=\"table\" style=\"grid-template-columns: ${this._getGridTemplateColumns()}\"> <div class=\"header-row\"> ${this.getDisplayedColumns().map((col, i) => html ` <div class=${classMap(this._getHeaderCellClasses(col, i))} style=${styleMap(this._getCellStyle(col, i))} data-column-id=${col.id} >${col.label ?? col.id}${col.resizable !== false ? html `<div class=\"header-cell__resize\" @mousedown=${(e) => this._handleResizeStart(e, col.id)} ></div>` : nothing}</div> `)} </div> ${this._renderFilterRow()} ${this._data.map((row, rowIndex) => { const cells = this.getDisplayedColumns().map((col, i) => html ` <div class=${classMap(this._getCellClasses(col, i))} style=${styleMap(this._getCellStyle(col, i))} data-column-id=${col.id} > ${this._renderCellContent(col, row, rowIndex)} </div> `); if (this._model.rowHref) { return html ` <a href=${this._model.rowHref(row)} class=${classMap({ 'row': true, 'row--clickable': true, 'row--link': true })} @mousedown=${() => this._handleRowMouseDown()} @click=${() => this._handleRowClick(row, rowIndex)} >${cells}</a> `; } return html ` <div class=${classMap({ 'row': true, 'row--clickable': !!this._model.rowClickable })} @mousedown=${() => this._handleRowMouseDown()} @click=${() => this._handleRowClick(row, rowIndex)} >${cells}</div> `; })} </div> </div> </div> `; } /** * Renders a data table with: * - Header bar with title, search input with view selector, and tools (pagination, refresh, column visibility, actions dropdown) * - Scrollable grid with sticky header row and optional sticky left/right columns * - Loading, error message, or empty state when no data */ render() { if (!this._model.columns.length) { return html `<slot></slot>`; } return html ` ${this._renderHeader()} ${this._renderTable()} ${this._renderFilterPanel()} `; } }"
2585
+ "default": "class KRTable extends LitElement { constructor() { super(...arguments); /** * Internal flag to switch between scroll edge modes: * - 'overlay': Fixed padding with overlay elements that hide content at edges (scrollbar at viewport edge) * - 'edge': Padding scrolls with content, allowing table to reach edges when scrolling */ this._scrollStyle = 'overlay'; this._data = []; this._dataState = 'idle'; this._page = 1; this._pageSize = 50; this._totalItems = 0; this._totalPages = 0; this._searchQuery = ''; this._canScrollLeft = false; this._canScrollRight = false; this._canScrollHorizontal = false; this._columnPickerOpen = false; this._filterPanelOpened = null; this._filterPanelTab = 'filter'; this._buckets = new Map(); this._filterPanelPos = { top: 0, left: 0 }; this._resizing = null; this._resizeObserver = null; this._searchPositionLocked = false; this._model = new KRTableModel(); this.def = { columns: [] }; this._handleClickOutside = (e) => { const path = e.composedPath(); if (this._columnPickerOpen) { const picker = this.shadowRoot?.querySelector('.column-picker-wrapper'); if (picker && !path.includes(picker)) { this._columnPickerOpen = false; } } if (this._filterPanelOpened) { if (!path.some((el) => el.classList?.contains('filter-panel'))) { this._handleFilterApply(); } } }; this._handleResizeMove = (e) => { if (!this._resizing) return; const col = this._model.columns.find(c => c.id === this._resizing.columnId); if (col) { const newWidth = this._resizing.startWidth + (e.clientX - this._resizing.startX); col.width = `${Math.min(900, Math.max(50, newWidth))}px`; this.requestUpdate(); } }; this._handleResizeEnd = () => { this._resizing = null; document.removeEventListener('mousemove', this._handleResizeMove); document.removeEventListener('mouseup', this._handleResizeEnd); }; } connectedCallback() { super.connectedCallback(); this.classList.toggle('kr-table--scroll-overlay', this._scrollStyle === 'overlay'); this.classList.toggle('kr-table--scroll-edge', this._scrollStyle === 'edge'); this._fetch(); this._initRefresh(); document.addEventListener('click', this._handleClickOutside); this._resizeObserver = new ResizeObserver(() => { // Unlock and recalculate on resize since layout changes this._searchPositionLocked = false; this._updateSearchPosition(); }); this._resizeObserver.observe(this); } disconnectedCallback() { super.disconnectedCallback(); clearInterval(this._refreshTimer); document.removeEventListener('click', this._handleClickOutside); this._resizeObserver?.disconnect(); } willUpdate(changedProperties) { if (changedProperties.has('def')) { // Build internal model from user-provided def this._model = new KRTableModel(); if (this.def.title) { this._model.title = this.def.title; } if (this.def.actions) { this._model.actions = this.def.actions; } if (this.def.data) { this._model.data = this.def.data; } if (this.def.dataSource) { this._model.dataSource = this.def.dataSource; } if (typeof this.def.refreshInterval === 'number') { this._model.refreshInterval = this.def.refreshInterval; } if (typeof this.def.pageSize === 'number') { this._model.pageSize = this.def.pageSize; } if (this.def.rowClickable) { this._model.rowClickable = this.def.rowClickable; } if (this.def.rowHref) { this._model.rowHref = this.def.rowHref; } this._model.columns = this.def.columns.map(col => { const column = { ...col, filter: null }; if (!column.type) { column.type = 'string'; } if (column.type === 'actions') { column.label = col.label ?? ''; column.sticky = 'right'; column.resizable = false; return column; } if (col.filterable || col.facetable) { column.filter = new KRQuery(); column.filter.field = col.id; column.filter.type = column.type; if (col.facetable && !col.filterable) { column.filter.operator = 'in'; column.filter.value = []; } else if (column.filter.type === 'string') { column.filter.operator = 'contains'; } } return column; }); if (this.def.displayedColumns) { this._model.displayedColumns = this.def.displayedColumns; } else { this._model.displayedColumns = this._model.columns.map(c => c.id); } this._fetch(); this._initRefresh(); } } updated(changedProperties) { this._updateScrollFlags(); this._syncSlottedContent(); } /** Syncs light DOM content for cells with custom render functions */ _syncSlottedContent() { const columns = this.getDisplayedColumns().filter(col => col.render); if (!columns.length) return; // Clear old slotted content this.querySelectorAll('[slot^=\"cell-\"]').forEach(el => el.remove()); // Create new slotted content this._data.forEach((row, rowIndex) => { columns.forEach(col => { const result = col.render(row); if (!result) return; const el = document.createElement('span'); el.slot = `cell-${rowIndex}-${col.id}`; if (col.type === 'actions') { el.style.display = 'flex'; el.style.gap = '8px'; } if (typeof result === 'string') { el.innerHTML = result; } else { render(result, el); } this.appendChild(el); }); }); } // ---------------------------------------------------------------------------- // Public Interface // ---------------------------------------------------------------------------- refresh() { this._fetch(); } goToPrevPage() { if (this._page > 1) { this._page--; this._fetch(); } } goToNextPage() { if (this._page < this._totalPages) { this._page++; this._fetch(); } } goToPage(page) { if (page >= 1 && page <= this._totalPages) { this._page = page; this._fetch(); } } // ---------------------------------------------------------------------------- // Data Fetching // ---------------------------------------------------------------------------- _toSolrData() { const request = { page: this._page - 1, size: this._pageSize, sorts: [], filterFields: [], queryFields: [], facetFields: [] }; for (const col of this._model.columns) { if (!col.filter || col.filter.isEmpty() || !col.filter.isValid()) { continue; } const filterData = col.filter.toSolrData(); if (col.facetable && col.filter.operator === 'in') { filterData.tagged = true; } request.filterFields.push(filterData); } for (const col of this._model.columns) { if (!col.facetable) { continue; } request.facetFields.push({ name: col.id, type: 'FIELD', limit: 100, sort: 'count', minimumCount: 1 }); } if (this._searchQuery?.trim().length) { request.queryFields.push({ name: '_text_', operation: 'IS', value: termify(this._searchQuery, false) }); } return request; } _toDbParams() { const request = { page: this._page - 1, size: this._pageSize, sorts: [], filterFields: [], queryFields: [], facetFields: [] }; for (const col of this._model.columns) { if (!col.filter || col.filter.isEmpty() || !col.filter.isValid()) { continue; } request.filterFields.push(col.filter.toDbParams()); } if (this._searchQuery?.trim().length) { this._model.columns.filter(col => col.searchable).forEach(col => { request.queryFields.push({ name: col.id, operation: 'CONTAINS', value: this._searchQuery, and: false }); }); } return request; } /** * Fetches data from the API and updates the table. * Shows a loading spinner while fetching, then displays rows on success * or an error snackbar on failure. * Request/response format depends on dataSource.mode (solr, opensearch, db). */ _fetch() { if (this._model.data) { this._data = this._model.data; this._totalItems = this._model.data.length; this._totalPages = Math.ceil(this._model.data.length / this._pageSize); this._dataState = 'success'; return; } if (!this._model.dataSource) return; this._dataState = 'loading'; let request; if (this._model.dataSource.mode === 'db') { request = this._toDbParams(); } else { request = this._toSolrData(); } this._model.dataSource.fetch(request) .then(response => { // Parse response based on mode switch (this._model.dataSource?.mode) { case 'opensearch': { throw Error('Opensearch not supported yet'); break; } case 'db': { const res = response; this._data = res.data.content; this._totalItems = res.data.totalElements; this._totalPages = res.data.totalPages; this._pageSize = res.data.size; break; } default: { // solr const res = response; this._data = res.data.content; this._totalItems = res.data.totalElements; this._totalPages = res.data.totalPages; this._pageSize = res.data.size; this._parseFacetResults(res); } } this._dataState = 'success'; this._updateSearchPosition(); }) .catch(err => { this._dataState = 'error'; KRSnackbar.show({ message: err instanceof Error ? err.message : 'Failed to load data', type: 'error' }); }); } _parseFacetResults(response) { if (!response.data.facetFields) { return; } for (const col of this._model.columns) { if (!col.facetable) { continue; } const rawBuckets = response.data.facetFields[col.id]; if (!rawBuckets) { this._buckets.set(col.id, []); continue; } const buckets = []; for (const raw of rawBuckets) { // Solr returns boolean facet values as strings — coerce to actual booleans // so they match the filter values stored by toggle(). let val = raw.name; if (col.type === 'boolean' && typeof raw.name === 'string') { if (raw.name === 'true') { val = true; } else if (raw.name === 'false') { val = false; } } if (raw.name === null && raw.count > 0) { buckets.unshift({ val: null, count: raw.count }); } if (raw.name !== null) { buckets.push({ val: val, count: raw.count }); } } // Bucket sync: ensure selected values appear even with 0 results if (col.filter && col.filter.operator === 'in' && Array.isArray(col.filter.value)) { for (const selectedVal of col.filter.value) { if (!buckets.some(b => b.val === selectedVal)) { buckets.push({ val: selectedVal, count: 0 }); } } } this._buckets.set(col.id, buckets); } // Trigger re-render since Map mutation doesn't trigger Lit updates this._buckets = new Map(this._buckets); } /** * Sets up auto-refresh so the table automatically fetches fresh data * at a regular interval (useful for dashboards, monitoring views). * Configured via def.refreshInterval in milliseconds. */ _initRefresh() { clearInterval(this._refreshTimer); if (this._model.refreshInterval && this._model.refreshInterval > 0) { this._refreshTimer = window.setInterval(() => { this._fetch(); }, this._model.refreshInterval); } } _handleSearch(e) { const input = e.target; this._searchQuery = input.value; this._page = 1; this._fetch(); } _getGridTemplateColumns() { const cols = this.getDisplayedColumns(); return cols.map((col) => { // If column has explicit width, use it if (col.width) { return col.width; } // Actions columns: fit content without minimum if (col.type === 'actions') { return 'max-content'; } // No width specified - use content-based sizing with minimum return 'minmax(80px, auto)'; }).join(' '); } /** * Updates search position to be centered with equal gaps from title and tools. * On first call: resets to flex centering, measures position, then locks with fixed margin. * Subsequent calls are ignored unless _searchPositionLocked is reset (e.g., on resize). */ _updateSearchPosition() { // Skip if already locked (prevents shifts on pagination changes) if (this._searchPositionLocked) return; const search = this.shadowRoot?.querySelector('.search'); const searchField = search?.querySelector('.search-field'); if (!search || !searchField) return; // Reset to flex centering search.style.justifyContent = 'center'; searchField.style.marginLeft = ''; requestAnimationFrame(() => { const searchRect = search.getBoundingClientRect(); const fieldRect = searchField.getBoundingClientRect(); // Calculate how far from the left of search container the field currently is const currentOffset = fieldRect.left - searchRect.left; // Lock position: switch to flex-start and use fixed margin search.style.justifyContent = 'flex-start'; searchField.style.marginLeft = `${currentOffset}px`; // Mark as locked so pagination changes don't shift the search this._searchPositionLocked = true; }); } // ---------------------------------------------------------------------------- // Columns // ---------------------------------------------------------------------------- _toggleColumnPicker() { this._columnPickerOpen = !this._columnPickerOpen; } _toggleColumn(columnId) { if (this._model.displayedColumns.includes(columnId)) { this._model.displayedColumns = this._model.displayedColumns.filter(id => id !== columnId); } else { this._model.displayedColumns = [...this._model.displayedColumns, columnId]; } } // Clear any existing text selection on mousedown so we only detect // selections made during this click gesture, not stale selections from elsewhere _handleRowMouseDown() { if (!this._model.rowClickable) { return; } window.getSelection()?.removeAllRanges(); } _handleRowClick(row, rowIndex) { if (!this._model.rowClickable) { return; } const selection = window.getSelection(); if (selection && selection.toString().length > 0) { return; } this.dispatchEvent(new CustomEvent('row-click', { detail: { row, rowIndex }, bubbles: true, composed: true })); } // When a user toggles a column on via the column picker, it gets appended // to _displayedColumns. By mapping over _displayedColumns (not def.columns), // the new column appears at the right edge of the table instead of jumping // back to its original position in the column definition. // Actions columns are always moved to the end. getDisplayedColumns() { return this._model.displayedColumns .map(id => this._model.columns.find(col => col.id === id)) .sort((a, b) => { if (a.type === 'actions' && b.type !== 'actions') return 1; if (a.type !== 'actions' && b.type === 'actions') return -1; return 0; }); } // ---------------------------------------------------------------------------- // Scrolling // ---------------------------------------------------------------------------- /** * Scroll event handler that updates scroll flags in real-time as user scrolls. * Updates shadow indicators to show if more content exists left/right. */ _handleScroll(e) { const container = e.target; this._canScrollLeft = container.scrollLeft > 0; this._canScrollRight = container.scrollLeft < container.scrollWidth - container.clientWidth - 1; } /** * Updates scroll state flags for the table content container. * - _canScrollLeft: true if scrolled right (can scroll back left) * - _canScrollRight: true if more content exists to the right * - _canScrollHorizontal: true if content is wider than container * These flags control scroll shadow indicators and CSS classes. */ _updateScrollFlags() { const container = this.shadowRoot?.querySelector('.content'); if (container) { this._canScrollLeft = container.scrollLeft > 0; this._canScrollRight = container.scrollWidth > container.clientWidth && container.scrollLeft < container.scrollWidth - container.clientWidth - 1; this._canScrollHorizontal = container.scrollWidth > container.clientWidth; } this.classList.toggle('kr-table--scroll-left-available', this._canScrollLeft); this.classList.toggle('kr-table--scroll-right-available', this._canScrollRight); this.classList.toggle('kr-table--scroll-horizontal-available', this._canScrollHorizontal); this.classList.toggle('kr-table--sticky-left', this.getDisplayedColumns().some(c => c.sticky === 'left')); this.classList.toggle('kr-table--sticky-right', this.getDisplayedColumns().some(c => c.sticky === 'right')); } // ---------------------------------------------------------------------------- // Column Resizing // ---------------------------------------------------------------------------- _handleResizeStart(e, columnId) { e.preventDefault(); const headerCell = this.shadowRoot?.querySelector(`.header-cell[data-column-id=\"${columnId}\"]`); this._resizing = { columnId, startX: e.clientX, startWidth: headerCell?.offsetWidth || 200 }; document.addEventListener('mousemove', this._handleResizeMove); document.addEventListener('mouseup', this._handleResizeEnd); } // ---------------------------------------------------------------------------- // Header // ---------------------------------------------------------------------------- _handleAction(action) { if (action.href) { return; } this.dispatchEvent(new CustomEvent('action', { detail: { action: action.id }, bubbles: true, composed: true })); } // ---------------------------------------------------------------------------- // Filter Handlers // ---------------------------------------------------------------------------- _handleKqlChange(e, column) { const kql = e.target.value.trim(); if (!kql) { column.filter.clear(); this.requestUpdate(); } else { column.filter.setKql(kql); this.requestUpdate(); if (!column.filter.isValid()) { return; } } this._page = 1; this._fetch(); } _handleFilterPanelToggle(e, column) { e.stopPropagation(); if (this._filterPanelOpened === column.id) { this._filterPanelOpened = null; } else { const rect = e.currentTarget.getBoundingClientRect(); let left = rect.left; if (left + 328 > window.innerWidth) { left = window.innerWidth - 328; } this._filterPanelPos = { top: rect.bottom + 4, left }; this._filterPanelOpened = column.id; if (column.facetable) { this._filterPanelTab = 'counts'; } else { this._filterPanelTab = 'filter'; } } } _handleKqlClear(column) { column.filter.clear(); this._page = 1; this._fetch(); } _handleFilterClear() { const column = this._model.columns.find(c => c.id === this._filterPanelOpened); if (column) { column.filter.clear(); if (column.facetable && !column.filterable) { column.filter.operator = 'in'; column.filter.value = []; } } this._filterPanelOpened = null; this._page = 1; this._fetch(); } _handleFilterTextKeydown(e, column) { if (e.key === 'Enter') { e.preventDefault(); this._handleFilterApply(); } } _handleOperatorChange(e, column) { column.filter.setOperator(e.target.value); this.requestUpdate(); } _handleFilterStringChange(e, column) { column.filter.setValue(e.target.value); this.requestUpdate(); } _handleFilterNumberChange(e, column) { column.filter.setValue(Number(e.target.value)); this.requestUpdate(); } _handleFilterDateChange(e, column) { column.filter.setValue(new Date(e.target.value), 'day'); this.requestUpdate(); } _handleFilterBooleanChange(e, column) { column.filter.setValue(e.target.value === 'true'); this.requestUpdate(); } _handleFilterDateStartChange(e, column) { column.filter.setStart(new Date(e.target.value), 'day'); this.requestUpdate(); } _handleFilterDateEndChange(e, column) { column.filter.setEnd(new Date(e.target.value), 'day'); this.requestUpdate(); } _handleFilterNumberStartChange(e, column) { column.filter.setStart(Number(e.target.value)); this.requestUpdate(); } _handleFilterNumberEndChange(e, column) { column.filter.setEnd(Number(e.target.value)); this.requestUpdate(); } _handleFilterListChange(e, column) { const items = e.target.value.split(',').map((v) => v.trim()).filter((v) => v !== ''); if (column.type === 'number') { column.filter.setValue(items.map((v) => Number(v))); } else { column.filter.setValue(items); } this.requestUpdate(); } _handleFilterApply() { this._filterPanelOpened = null; this._page = 1; this._fetch(); } _handleFilterPanelTabChange(e) { this._filterPanelTab = e.detail.activeTabId; } _handleBucketToggle(e, column, bucket) { column.filter.toggle(bucket.val); this._page = 1; this._fetch(); } // ---------------------------------------------------------------------------- // Rendering // ---------------------------------------------------------------------------- _renderCellContent(column, row, rowIndex) { const value = row[column.id]; if (column.render) { // Use slot to project content from light DOM so external styles apply return html `<slot name=\"cell-${rowIndex}-${column.id}\"></slot>`; } if (value === null || value === undefined) { return ''; } switch (column.type) { case 'number': if (column.format === 'currency' && typeof value === 'number') { return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' }); } return String(value); case 'date': { let date; if (value instanceof Date) { date = value; } else if (typeof value === 'string' && /^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}/.test(value)) { // MySQL datetime format (UTC): \"2026-01-28 01:33:44:517\" // Replace last colon before ms with dot, append Z for UTC const isoString = value.replace(/(\\d{2}:\\d{2}:\\d{2}):(\\d+)$/, '$1.$2').replace(' ', 'T') + 'Z'; date = new Date(isoString); } else { date = new Date(value); } // Show date and time for datetime values in UTC return date.toLocaleString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit', timeZone: 'UTC' }); } case 'boolean': if (value === true) return 'Yes'; if (value === false) return 'No'; return ''; default: return String(value); } } /** * Returns CSS classes for a header cell based on column config. */ _getHeaderCellClasses(column, index) { return { 'header-cell': true, 'header-cell--align-center': column.align === 'center', 'header-cell--align-right': column.align === 'right', 'header-cell--sticky-left': column.sticky === 'left', 'header-cell--sticky-left-last': column.sticky === 'left' && !this.getDisplayedColumns().slice(index + 1).some(c => c.sticky === 'left'), 'header-cell--sticky-right': column.sticky === 'right', 'header-cell--sticky-right-first': column.sticky === 'right' && !this.getDisplayedColumns().slice(0, index).some(c => c.sticky === 'right') }; } /** * Returns CSS classes for a table cell based on column config: * - Alignment (center, right) * - Sticky positioning (left, right) * - Border classes for the last left-sticky or first right-sticky column */ _getCellClasses(column, index) { return { 'cell': true, 'cell--actions': column.type === 'actions', 'cell--align-center': column.align === 'center', 'cell--align-right': column.align === 'right', 'cell--sticky-left': column.sticky === 'left', 'cell--sticky-left-last': column.sticky === 'left' && !this.getDisplayedColumns().slice(index + 1).some(c => c.sticky === 'left'), 'cell--sticky-right': column.sticky === 'right', 'cell--sticky-right-first': column.sticky === 'right' && !this.getDisplayedColumns().slice(0, index).some(c => c.sticky === 'right') }; } /** * Returns inline styles for a table cell: * - Width (from column config or default 150px) * - Min-width (if specified) * - Left/right offset for sticky columns (calculated from widths of preceding sticky columns) */ _getCellStyle(column, index) { const styles = {}; if (column.sticky === 'left') { let leftOffset = 0; for (let i = 0; i < index; i++) { const col = this.getDisplayedColumns()[i]; if (col.sticky === 'left') { leftOffset += parseInt(col.width || '0', 10); } } styles.left = `${leftOffset}px`; } if (column.sticky === 'right') { let rightOffset = 0; for (let i = index + 1; i < this.getDisplayedColumns().length; i++) { const col = this.getDisplayedColumns()[i]; if (col.sticky === 'right') { rightOffset += parseInt(col.width || '0', 10); } } styles.right = `${rightOffset}px`; } return styles; } /** * Renders the pagination controls: * - Previous page arrow (disabled on first page) * - Range text showing \"1-50 of 150\" format * - Next page arrow (disabled on last page) * * Hidden when there's no data or all data fits on one page. */ _renderPagination() { const start = (this._page - 1) * this._pageSize + 1; const end = Math.min(this._page * this._pageSize, this._totalItems); return html ` <div class=\"pagination\"> <span class=\"pagination-icon ${this._page === 1 ? 'pagination-icon--disabled' : ''}\" @click=${this.goToPrevPage} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z\"/></svg> </span> <span class=\"pagination-info\">${start}-${end} of ${this._totalItems}</span> <span class=\"pagination-icon ${this._page === this._totalPages ? 'pagination-icon--disabled' : ''}\" @click=${this.goToNextPage} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z\"/></svg> </span> </div> `; } /** * Renders the header toolbar containing: * - Title (left) * - Search bar with view selector dropdown (center) * - Tools (right): page navigation, refresh button, column visibility picker, actions dropdown * * Hidden when there's no title, no actions, and data fits on one page. */ _renderHeader() { if (!this._model.title && !this._model.actions?.length && this._totalPages <= 1) { return nothing; } return html ` <div class=\"header\"> <div class=\"title\">${this._model.title ?? ''}</div> ${this._model.dataSource?.mode === 'db' && !this._model.columns.some(col => col.searchable) ? html `<div class=\"search\"></div>` : html ` <div class=\"search\"> <!-- TODO: Saved views dropdown <div class=\"views\"> <span>Default View</span> <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z\"/></svg> </div> --> <div class=\"search-field\"> <svg class=\"search-icon\" viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z\"/></svg> <input type=\"text\" class=\"search-input\" placeholder=\"Search...\" .value=${this._searchQuery} @input=${this._handleSearch} /> </div> </div> `} <div class=\"tools\"> ${this._renderPagination()} <span class=\"refresh\" title=\"Refresh\" @click=${() => this.refresh()}> <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z\"/></svg> </span> <div class=\"column-picker-wrapper\"> <span class=\"header-icon\" title=\"Columns\" @click=${this._toggleColumnPicker}> <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\"><path d=\"M121-280v-400q0-33 23.5-56.5T201-760h559q33 0 56.5 23.5T840-680v400q0 33-23.5 56.5T760-200H201q-33 0-56.5-23.5T121-280Zm79 0h133v-400H200v400Zm213 0h133v-400H413v400Zm213 0h133v-400H626v400Z\"/></svg> </span> <div class=\"column-picker ${this._columnPickerOpen ? 'open' : ''}\"> ${[...this._model.columns].filter(col => col.type !== 'actions').sort((a, b) => (a.label ?? a.id).localeCompare(b.label ?? b.id)).map(col => html ` <div class=\"column-picker-item\" @click=${() => this._toggleColumn(col.id)}> <div class=\"column-picker-checkbox ${this._model.displayedColumns.includes(col.id) ? 'checked' : ''}\"> <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/></svg> </div> <span class=\"column-picker-label\">${col.label ?? col.id}</span> </div> `)} </div> </div> ${this._model.actions?.length === 1 ? html ` <kr-button class=\"actions\" .href=${this._model.actions[0].href} .target=${this._model.actions[0].target} @click=${() => this._handleAction(this._model.actions[0])} > ${this._model.actions[0].label} </kr-button> ` : this._model.actions?.length ? html ` <kr-button class=\"actions\" .options=${this._model.actions.map(a => ({ id: a.id, label: a.label }))} @option-select=${(e) => this._handleAction({ id: e.detail.id, label: e.detail.label })} > Actions </kr-button> ` : nothing} </div> </div> `; } /** Renders status message (loading, error, empty) */ _renderStatus() { if (this._dataState === 'loading' && this._data.length === 0) { return html `<div class=\"status\">Loading...</div>`; } if (this._dataState === 'error' && this._data.length === 0) { return html `<div class=\"status status--error\">Error loading data</div>`; } if (this._data.length === 0) { return html `<div class=\"status\">No data available</div>`; } return nothing; } _renderFilterPanel() { if (!this._filterPanelOpened) { return nothing; } const column = this._model.columns.find(c => c.id === this._filterPanelOpened); // Build filter content (operator + value input) let valueInput = html ``; if (column.filter.operator === 'empty' || column.filter.operator === 'n_empty') { valueInput = html ` <input type=\"text\" class=\"filter-panel__input\" disabled .value=${column.filter.text} /> `; } else if (column.filter.operator === 'between' && column.type === 'date') { valueInput = html ` <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${column.filter.value?.start ?? null} @change=${(e) => this._handleFilterDateStartChange(e, column)} /> <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${column.filter.value?.end ?? null} @change=${(e) => this._handleFilterDateEndChange(e, column)} /> `; } else if (column.filter.operator === 'between' && column.type === 'number') { valueInput = html ` <input type=\"number\" class=\"filter-panel__input\" placeholder=\"Start\" .value=${column.filter.value?.start ?? ''} @input=${(e) => this._handleFilterNumberStartChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> <input type=\"number\" class=\"filter-panel__input\" placeholder=\"End\" .value=${column.filter.value?.end ?? ''} @input=${(e) => this._handleFilterNumberEndChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> `; } else if (column.filter.operator === 'in') { valueInput = html ` <textarea class=\"filter-panel__textarea\" rows=\"3\" placeholder=\"Values (comma-separated)\" .value=${column.filter.text} @input=${(e) => this._handleFilterListChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} ></textarea> `; } else if (column.type === 'boolean') { valueInput = html ` <kr-select-field placeholder=\"Value\" .value=${String(column.filter.value ?? '')} @change=${(e) => this._handleFilterBooleanChange(e, column)} > <kr-select-option value=\"true\">Yes</kr-select-option> <kr-select-option value=\"false\">No</kr-select-option> </kr-select-field> `; } else if (column.type === 'date') { valueInput = html ` <input type=\"date\" class=\"filter-panel__input\" .valueAsDate=${column.filter.value} @change=${(e) => this._handleFilterDateChange(e, column)} /> `; } else if (column.type === 'number') { valueInput = html ` <input type=\"number\" class=\"filter-panel__input\" placeholder=\"Value\" min=\"0\" .value=${column.filter.text} @input=${(e) => this._handleFilterNumberChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> `; } else { valueInput = html ` <input type=\"text\" class=\"filter-panel__input\" placeholder=\"Value\" .value=${column.filter.text} @input=${(e) => this._handleFilterStringChange(e, column)} @keydown=${(e) => this._handleFilterTextKeydown(e, column)} /> `; } const filterContent = html ` <div class=\"filter-panel__content\"> <kr-select-field .value=${column.filter.operator} @change=${(e) => this._handleOperatorChange(e, column)} > ${getOperatorsForType(column.type).map(op => html ` <kr-select-option value=${op.key}>${op.label}</kr-select-option> `)} </kr-select-field> ${valueInput} </div> `; // Build bucket list content const buckets = this._buckets.get(column.id) || []; let bucketContent; if (!buckets.length) { bucketContent = html `<div class=\"bucket-empty\">No data</div>`; } else { bucketContent = html ` <div class=\"buckets\"> ${buckets.map(bucket => { let bucketLabel = '(Empty)'; if (bucket.val !== null && bucket.val !== undefined) { if (column.type === 'boolean') { if (bucket.val === true || bucket.val === 'true') { bucketLabel = 'Yes'; } else { bucketLabel = 'No'; } } else { bucketLabel = String(bucket.val); } } let checkIcon = nothing; if (column.filter.has(bucket.val)) { checkIcon = html ` <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z\"/> </svg> `; } return html ` <div class=\"bucket\" @click=${(e) => this._handleBucketToggle(e, column, bucket)} > <div class=${classMap({ 'bucket__checkbox': true, 'bucket__checkbox--checked': column.filter.has(bucket.val) })}> ${checkIcon} </div> <span class=\"bucket__label\">${bucketLabel}</span> <span class=\"bucket__count\">${bucket.count}</span> </div> `; })} </div> `; } // Build panel body — tabs if both filterable+facetable, otherwise just the relevant content let panelBody; if (column.facetable && column.filterable) { panelBody = html ` <kr-tab-group size=\"small\" active-tab-id=${this._filterPanelTab} @tab-change=${(e) => this._handleFilterPanelTabChange(e)} > <kr-tab id=\"filter\" label=\"Filter\"> ${filterContent} </kr-tab> <kr-tab id=\"counts\" label=\"Counts\"> ${bucketContent} </kr-tab> </kr-tab-group> `; } else if (column.facetable) { panelBody = bucketContent; } else { panelBody = filterContent; } return html ` <div class=\"filter-panel\" style=${styleMap({ top: this._filterPanelPos.top + 'px', left: this._filterPanelPos.left + 'px' })} > ${panelBody} <div class=\"filter-panel__actions\"> <kr-button variant=\"outline\" color=\"secondary\" size=\"small\" @click=${this._handleFilterClear}> Clear </kr-button> <kr-button size=\"small\" @click=${this._handleFilterApply}> Apply </kr-button> </div> </div> `; } /** * Renders filter row below column headers. * Only displays for columns with filterable: true. */ _renderFilterRow() { const columns = this.getDisplayedColumns(); if (!columns.some(col => col.filterable || col.facetable)) { return nothing; } return html ` <div class=\"filter-row\"> ${columns.map((col, i) => { if (!col.filterable && !col.facetable) { return html `<div class=${classMap({ 'filter-cell': true, 'filter-cell--sticky-left': col.sticky === 'left', 'filter-cell--sticky-right': col.sticky === 'right', 'filter-cell--sticky-right-first': col.sticky === 'right' && !columns.slice(0, i).some((c) => c.sticky === 'right') })} style=${styleMap(this._getCellStyle(col, i))} ></div>`; } return html ` <div class=${classMap({ 'filter-cell': true, 'filter-cell--sticky-left': col.sticky === 'left', 'filter-cell--sticky-right': col.sticky === 'right', 'filter-cell--sticky-right-first': col.sticky === 'right' && !columns.slice(0, i).some((c) => c.sticky === 'right') })} style=${styleMap(this._getCellStyle(col, i))} > <div class=\"filter-cell__wrapper\"> <input type=\"text\" class=${classMap({ 'filter-cell__input': true, 'filter-cell__input--invalid': !col.filter.isValid() })} .value=${col.filter.kql} @change=${(e) => this._handleKqlChange(e, col)} /> ${col.filter?.kql?.length > 0 ? html ` <button class=\"filter-cell__clear\" @click=${() => this._handleKqlClear(col)} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/> </svg> </button> ` : nothing} <button class=${classMap({ 'filter-cell__advanced': true, 'filter-cell__advanced--opened': this._filterPanelOpened === col.id })} @click=${(e) => this._handleFilterPanelToggle(e, col)} > <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z\"/> </svg> </button> </div> </div> `; })} </div> `; } /** Renders the scrollable data grid with column headers and rows. */ _renderTable() { return html ` <div class=\"wrapper\"> <div class=\"overlay-left\"></div> <div class=\"overlay-right\"></div> ${this._renderStatus()} <div class=\"content\" @scroll=${this._handleScroll}> <div class=\"table\" style=\"grid-template-columns: ${this._getGridTemplateColumns()}\"> <div class=\"header-row\"> ${this.getDisplayedColumns().map((col, i) => html ` <div class=${classMap(this._getHeaderCellClasses(col, i))} style=${styleMap(this._getCellStyle(col, i))} data-column-id=${col.id} >${col.label ?? col.id}${col.resizable !== false ? html `<div class=\"header-cell__resize\" @mousedown=${(e) => this._handleResizeStart(e, col.id)} ></div>` : nothing}</div> `)} </div> ${this._renderFilterRow()} ${this._data.map((row, rowIndex) => { const cells = this.getDisplayedColumns().map((col, i) => html ` <div class=${classMap(this._getCellClasses(col, i))} style=${styleMap(this._getCellStyle(col, i))} data-column-id=${col.id} > ${this._renderCellContent(col, row, rowIndex)} </div> `); if (this._model.rowHref) { return html ` <a href=${this._model.rowHref(row)} class=${classMap({ 'row': true, 'row--clickable': true, 'row--link': true })} @mousedown=${() => this._handleRowMouseDown()} @click=${() => this._handleRowClick(row, rowIndex)} >${cells}</a> `; } return html ` <div class=${classMap({ 'row': true, 'row--clickable': !!this._model.rowClickable })} @mousedown=${() => this._handleRowMouseDown()} @click=${() => this._handleRowClick(row, rowIndex)} >${cells}</div> `; })} </div> </div> </div> `; } /** * Renders a data table with: * - Header bar with title, search input with view selector, and tools (pagination, refresh, column visibility, actions dropdown) * - Scrollable grid with sticky header row and optional sticky left/right columns * - Loading, error message, or empty state when no data */ render() { if (!this._model.columns.length) { return html `<slot></slot>`; } return html ` ${this._renderHeader()} ${this._renderTable()} ${this._renderFilterPanel()} `; } }"
2568
2586
  }
2569
2587
  ],
2570
2588
  "exports": [
@@ -4410,6 +4428,14 @@
4410
4428
  "name": "KRAutoSuggest",
4411
4429
  "module": "./auto-suggest/auto-suggest.js"
4412
4430
  }
4431
+ },
4432
+ {
4433
+ "kind": "js",
4434
+ "name": "KRComboBox",
4435
+ "declaration": {
4436
+ "name": "KRComboBox",
4437
+ "module": "./combo-box/combo-box.js"
4438
+ }
4413
4439
  }
4414
4440
  ]
4415
4441
  },
@@ -6621,7 +6647,7 @@
6621
6647
  {
6622
6648
  "kind": "variable",
6623
6649
  "name": "KRAutoSuggest",
6624
- "default": "class KRAutoSuggest extends LitElement { constructor() { super(); this.label = ''; this.name = ''; this.value = ''; this.placeholder = ''; this.disabled = false; this.readonly = false; this.required = false; this.hint = ''; this.options = []; this.statusType = 'finished'; this.loadingText = 'Loading...'; this.errorText = 'Error loading options'; this.emptyText = 'No matches found'; this.filteringType = 'auto'; this.highlightMatches = true; this._isOpen = false; this._highlightedIndex = -1; this._touched = false; this._dirty = false; this._handleDocumentClick = this._onDocumentClick.bind(this); this._internals = this.attachInternals(); } connectedCallback() { super.connectedCallback(); document.addEventListener('click', this._handleDocumentClick); } disconnectedCallback() { super.disconnectedCallback(); document.removeEventListener('click', this._handleDocumentClick); } firstUpdated() { this._updateFormValue(); } get form() { return this._internals.form; } get validity() { return this._internals.validity; } get validationMessage() { return this._internals.validationMessage; } checkValidity() { return this._internals.checkValidity(); } reportValidity() { return this._internals.reportValidity(); } formResetCallback() { this.value = ''; this._touched = false; this._dirty = false; this._isOpen = false; this._highlightedIndex = -1; this._updateFormValue(); } formStateRestoreCallback(state) { this.value = state; } get _filteredOptions() { if (this.filteringType === 'manual' || !this.value) { return this.options; } const searchValue = this.value.toLowerCase(); const filtered = []; for (const option of this.options) { if (isGroup(option)) { const filteredGroupOptions = option.options.filter((opt) => { const label = (opt.label || opt.value).toLowerCase(); const tags = opt.filteringTags?.join(' ').toLowerCase() || ''; return label.includes(searchValue) || tags.includes(searchValue); }); if (filteredGroupOptions.length > 0) { filtered.push({ ...option, options: filteredGroupOptions }); } } else { const label = (option.label || option.value).toLowerCase(); const tags = option.filteringTags?.join(' ').toLowerCase() || ''; if (label.includes(searchValue) || tags.includes(searchValue)) { filtered.push(option); } } } return filtered; } get _flatOptions() { const flat = []; for (const option of this._filteredOptions) { if (isGroup(option)) { flat.push(...option.options); } else { flat.push(option); } } return flat; } _updateFormValue() { this._internals.setFormValue(this.value); // Validation if (this.required && !this.value) { this._internals.setValidity({ valueMissing: true }, 'This field is required', this._input); } else { this._internals.setValidity({}); } } _onInput(e) { const target = e.target; this.value = target.value; this._dirty = true; this._isOpen = true; this._highlightedIndex = -1; this._updateFormValue(); this.dispatchEvent(new CustomEvent('change', { detail: { value: this.value }, bubbles: true, composed: true, })); if (this.filteringType === 'manual') { this.dispatchEvent(new CustomEvent('load-items', { detail: { filteringText: this.value }, bubbles: true, composed: true, })); } } _onFocus() { this._isOpen = true; if (this.filteringType === 'manual') { this.dispatchEvent(new CustomEvent('load-items', { detail: { filteringText: this.value }, bubbles: true, composed: true, })); } } _onBlur() { this._touched = true; // Delay to allow click on option setTimeout(() => { this._isOpen = false; this._highlightedIndex = -1; }, 200); this._updateFormValue(); } _onKeyDown(e) { const options = this._flatOptions; switch (e.key) { case 'ArrowDown': e.preventDefault(); this._isOpen = true; this._highlightedIndex = Math.min(this._highlightedIndex + 1, options.length - 1); this._scrollToHighlighted(); break; case 'ArrowUp': e.preventDefault(); this._highlightedIndex = Math.max(this._highlightedIndex - 1, -1); this._scrollToHighlighted(); break; case 'Enter': if (this._highlightedIndex >= 0 && options[this._highlightedIndex]) { e.preventDefault(); this._selectOption(options[this._highlightedIndex]); } break; case 'Escape': e.preventDefault(); this._isOpen = false; this._highlightedIndex = -1; break; case 'Tab': this._isOpen = false; this._highlightedIndex = -1; break; } } _scrollToHighlighted() { this.updateComplete.then(() => { const container = this.shadowRoot?.querySelector('.dropdown'); const highlighted = this.shadowRoot?.querySelector('.option.is-highlighted'); if (container && highlighted) { const containerRect = container.getBoundingClientRect(); const highlightedRect = highlighted.getBoundingClientRect(); if (highlightedRect.bottom > containerRect.bottom) { highlighted.scrollIntoView({ block: 'nearest' }); } else if (highlightedRect.top < containerRect.top) { highlighted.scrollIntoView({ block: 'nearest' }); } } }); } _selectOption(option) { if (option.disabled) return; this.value = option.label || option.value; this._isOpen = false; this._highlightedIndex = -1; this._dirty = true; this._updateFormValue(); this.dispatchEvent(new CustomEvent('select', { detail: { value: option.value, selectedOption: option }, bubbles: true, composed: true, })); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); } _handleClear() { this.value = ''; this._updateFormValue(); this.dispatchEvent(new CustomEvent('change', { detail: { value: '' }, bubbles: true, composed: true, })); this._input?.focus(); } _onDocumentClick(e) { const path = e.composedPath(); if (!path.includes(this)) { this._isOpen = false; } } _highlightMatch(text) { if (!this.value || this.filteringType === 'manual' || !this.highlightMatches) { return html `${text}`; } const searchValue = this.value.toLowerCase(); const index = text.toLowerCase().indexOf(searchValue); if (index === -1) { return html `${text}`; } const before = text.slice(0, index); const match = text.slice(index, index + this.value.length); const after = text.slice(index + this.value.length); return html `${before}<span class=\"highlight\">${match}</span>${after}`; } _renderOption(option, index) { const isHighlighted = index === this._highlightedIndex; return html ` <button class=${classMap({ option: true, 'is-highlighted': isHighlighted, })} type=\"button\" role=\"option\" aria-selected=${isHighlighted} ?disabled=${option.disabled} @click=${() => this._selectOption(option)} @mouseenter=${() => { this._highlightedIndex = index; }} > <div class=\"option-content\"> <div class=\"option-label\"> ${this._highlightMatch(option.label || option.value)} ${option.labelTag ? html `<span class=\"option-tag\">${option.labelTag}</span>` : nothing} </div> ${option.description ? html `<div class=\"option-description\">${option.description}</div>` : nothing} ${option.tags && option.tags.length > 0 ? html ` <div class=\"option-tags\"> ${option.tags.map((tag) => html `<span class=\"option-tag\">${tag}</span>`)} </div> ` : nothing} </div> </button> `; } _renderDropdownContent() { if (this.statusType === 'loading') { return html ` <div class=\"status\"> <div class=\"spinner\"></div> ${this.loadingText} </div> `; } if (this.statusType === 'error') { return html ` <div class=\"status is-error\"> <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\"> <path d=\"M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z\" /> </svg> ${this.errorText} </div> `; } const filtered = this._filteredOptions; if (filtered.length === 0) { return html `<div class=\"empty\">${this.emptyText}</div>`; } let optionIndex = 0; return html ` ${filtered.map((option) => { if (isGroup(option)) { const groupOptions = option.options.map((opt) => { const rendered = this._renderOption(opt, optionIndex); optionIndex++; return rendered; }); return html ` <div class=\"group-label\">${option.label}</div> ${groupOptions} `; } const rendered = this._renderOption(option, optionIndex); optionIndex++; return rendered; })} `; } render() { const hasError = this._touched && !this.validity.valid; return html ` <div class=\"field-wrapper\"> ${this.label ? html ` <label> ${this.label} ${this.required ? html `<span class=\"required\">*</span>` : nothing} </label> ` : nothing} <div class=\"input-container\"> <div class=\"input-wrapper\"> <input type=\"text\" .value=${live(this.value)} placeholder=${ifDefined(this.placeholder || undefined)} ?disabled=${this.disabled} ?readonly=${this.readonly} ?required=${this.required} name=${ifDefined(this.name || undefined)} autocomplete=\"off\" role=\"combobox\" aria-autocomplete=\"list\" aria-expanded=${this._isOpen} aria-controls=\"dropdown\" class=${classMap({ 'input--invalid': hasError, })} @input=${this._onInput} @blur=${this._onBlur} @focus=${this._onFocus} @keydown=${this._onKeyDown} /> <div class=\"icon-container\"> ${this.value && !this.disabled && !this.readonly ? html ` <button class=\"clear-button\" type=\"button\" aria-label=\"Clear\" @click=${this._handleClear} > <svg width=\"16\" height=\"16\" fill=\"currentColor\" viewBox=\"0 0 16 16\"> <path d=\"M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z\" /> </svg> </button> ` : ''} ${!this.value && !this.disabled && !this.readonly ? html ` <svg class=\"search-icon\" fill=\"currentColor\" viewBox=\"0 0 16 16\"> <path d=\"M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z\" /> </svg> ` : ''} </div> </div> <div id=\"dropdown\" role=\"listbox\" class=${classMap({ dropdown: true, 'is-open': this._isOpen, })} > ${this._renderDropdownContent()} </div> </div> ${hasError ? html `<div class=\"validation-message\">${this.validationMessage}</div>` : this.hint ? html `<div class=\"hint\">${this.hint}</div>` : nothing} </div> `; } }"
6650
+ "default": "class KRAutoSuggest extends LitElement { constructor() { super(); this._requestId = 0; this._handleDocumentClick = (e) => { const path = e.composedPath(); if (!path.includes(this)) { this._isOpen = false; } }; this.label = ''; this.name = ''; this.value = ''; this.placeholder = ''; this.disabled = false; this.readonly = false; this.required = false; this.hint = ''; this.options = []; this.fetch = null; this._isOpen = false; this._highlightedIndex = -1; this._touched = false; this._handleInvalid = (e) => { e.preventDefault(); this._touched = true; }; this._internals = this.attachInternals(); } connectedCallback() { super.connectedCallback(); document.addEventListener('click', this._handleDocumentClick); this.addEventListener('invalid', this._handleInvalid); } disconnectedCallback() { super.disconnectedCallback(); document.removeEventListener('click', this._handleDocumentClick); this.removeEventListener('invalid', this._handleInvalid); } firstUpdated() { this._updateValidity(); } updated(changedProperties) { if (changedProperties.has('required') || changedProperties.has('value')) { this._updateValidity(); } if (changedProperties.has('options') && this._isOpen) { this._positionDropdown(); } } get form() { return this._internals.form; } get validity() { return this._internals.validity; } get validationMessage() { return this._internals.validationMessage; } checkValidity() { return this._internals.checkValidity(); } reportValidity() { return this._internals.reportValidity(); } formResetCallback() { this.value = ''; this._touched = false; this._isOpen = false; this._highlightedIndex = -1; this._internals.setFormValue(''); this._internals.setValidity({}); } formStateRestoreCallback(state) { this.value = state; } _updateValidity() { if (this._input) { this._internals.setValidity(this._input.validity, this._input.validationMessage); } } _fetch() { if (!this.fetch) { return; } this._requestId++; const requestId = this._requestId; this.fetch(this.value).then((options) => { if (requestId === this._requestId) { this.options = options; } }).catch((error) => { console.error('kr-auto-suggest: fetch failed', error); }); } _handleInput(e) { this.value = e.target.value; this._isOpen = true; this._highlightedIndex = -1; this._positionDropdown(); this._internals.setFormValue(this.value); this._internals.setValidity(this._input.validity, this._input.validationMessage); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); this._fetch(); } _positionDropdown() { requestAnimationFrame(() => { const dropdown = this.shadowRoot?.querySelector('.dropdown'); if (!dropdown) { return; } const inputRect = this._input.getBoundingClientRect(); const spaceBelow = window.innerHeight - inputRect.bottom - 4 - 8; const spaceAbove = inputRect.top - 4 - 8; dropdown.style.left = inputRect.left + 'px'; dropdown.style.width = inputRect.width + 'px'; // Open above the input if there's more room above than below if (spaceAbove > spaceBelow) { dropdown.style.top = ''; dropdown.style.bottom = (window.innerHeight - inputRect.top + 4) + 'px'; dropdown.style.maxHeight = spaceAbove + 'px'; } else { dropdown.style.bottom = ''; dropdown.style.top = inputRect.bottom + 4 + 'px'; dropdown.style.maxHeight = spaceBelow + 'px'; } }); } _handleFocus() { this._isOpen = true; this._positionDropdown(); this._fetch(); } _handleBlur() { this._touched = true; this._internals.setValidity(this._input.validity, this._input.validationMessage); // Delay to allow click on option setTimeout(() => { this._isOpen = false; this._highlightedIndex = -1; }, 200); } _handleKeyDown(e) { switch (e.key) { case 'ArrowDown': e.preventDefault(); this._isOpen = true; this._highlightedIndex = Math.min(this._highlightedIndex + 1, this.options.length - 1); this._scrollToHighlighted(); break; case 'ArrowUp': e.preventDefault(); if (this._highlightedIndex === -1) { this._isOpen = true; this._highlightedIndex = this.options.length - 1; } else { this._highlightedIndex = Math.max(this._highlightedIndex - 1, 0); } this._scrollToHighlighted(); break; case 'Enter': if (this._highlightedIndex >= 0 && this.options[this._highlightedIndex]) { e.preventDefault(); this._selectOption(this.options[this._highlightedIndex]); } break; case 'Escape': e.preventDefault(); this._isOpen = false; this._highlightedIndex = -1; break; case 'Tab': this._isOpen = false; this._highlightedIndex = -1; break; } } _scrollToHighlighted() { this.updateComplete.then(() => { const container = this.shadowRoot?.querySelector('.dropdown'); const highlighted = this.shadowRoot?.querySelector('.option--highlighted'); if (container && highlighted) { const containerRect = container.getBoundingClientRect(); const highlightedRect = highlighted.getBoundingClientRect(); if (highlightedRect.bottom > containerRect.bottom) { highlighted.scrollIntoView({ block: 'nearest' }); } else if (highlightedRect.top < containerRect.top) { highlighted.scrollIntoView({ block: 'nearest' }); } } }); } _selectOption(option) { if (option.disabled) { return; } this.value = option.value; this._isOpen = false; this._highlightedIndex = -1; this._internals.setFormValue(this.value); this.dispatchEvent(new CustomEvent('select', { detail: { value: option.value, option: option }, bubbles: true, composed: true, })); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); } _handleClear() { this.value = ''; this._internals.setFormValue(this.value); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); this._input?.focus(); } _handleOptionMouseEnter(e, index) { this._highlightedIndex = index; } _renderOption(option, index) { return html ` <button class=${classMap({ option: true, 'option--highlighted': index === this._highlightedIndex, })} type=\"button\" role=\"option\" aria-selected=${index === this._highlightedIndex} ?disabled=${option.disabled} @click=${(e) => this._selectOption(option)} @mouseenter=${(e) => this._handleOptionMouseEnter(e, index)} > ${option.label || option.value} </button> `; } render() { let footer = nothing; if (this._touched && this._input && !this._input.validity.valid) { footer = html `<div class=\"validation-message\">${this._input.validationMessage}</div>`; } else if (this.hint) { footer = html `<div class=\"hint\">${this.hint}</div>`; } return html ` <div class=\"wrapper\"> ${this.label ? html ` <label> ${this.label} ${this.required ? html `<span class=\"required\">*</span>` : nothing} </label> ` : nothing} <div class=\"input-wrapper\"> <input type=\"text\" .value=${live(this.value)} placeholder=${this.placeholder} ?disabled=${this.disabled} ?readonly=${this.readonly} ?required=${this.required} name=${this.name} autocomplete=\"off\" role=\"combobox\" aria-autocomplete=\"list\" aria-expanded=${this._isOpen} aria-controls=\"dropdown\" class=${classMap({ 'input--invalid': this._touched && this._input && !this._input.validity.valid, })} @input=${this._handleInput} @blur=${this._handleBlur} @focus=${this._handleFocus} @keydown=${this._handleKeyDown} /> <div class=\"icon-wrapper\"> ${this.value && !this.disabled && !this.readonly ? html ` <svg class=\"clear\" viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-label=\"Clear\" @click=${this._handleClear}> <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/> </svg> ` : nothing} ${!this.value && !this.disabled && !this.readonly ? html ` <svg class=\"search-icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z\"/> </svg> ` : nothing} </div> </div> <div id=\"dropdown\" role=\"listbox\" class=${classMap({ dropdown: true, 'dropdown--open': this._isOpen && this.options.length > 0, })} > ${this.options.map((option, index) => this._renderOption(option, index))} </div> ${footer} </div> `; } }"
6625
6651
  }
6626
6652
  ],
6627
6653
  "exports": [
@@ -6635,6 +6661,27 @@
6635
6661
  }
6636
6662
  ]
6637
6663
  },
6664
+ {
6665
+ "kind": "javascript-module",
6666
+ "path": "dist/form/combo-box/combo-box.js",
6667
+ "declarations": [
6668
+ {
6669
+ "kind": "variable",
6670
+ "name": "KRComboBox",
6671
+ "default": "class KRComboBox extends LitElement { constructor() { super(); this._requestId = 0; this._selectedOption = null; this._handleDocumentClick = (e) => { if (!e.composedPath().includes(this)) { this._close(); } }; this.label = ''; this.name = ''; this.value = ''; this.placeholder = 'Select an option'; this.disabled = false; this.readonly = false; this.required = false; this.hint = ''; this.optionValue = 'value'; this.optionLabel = 'label'; this.options = []; this.fetch = null; this.fetchSelection = null; this._isOpen = false; this._highlightedIndex = -1; this._touched = false; this._searchQuery = ''; this._handleInvalid = (e) => { e.preventDefault(); this._touched = true; }; this._internals = this.attachInternals(); } connectedCallback() { super.connectedCallback(); document.addEventListener('click', this._handleDocumentClick); this.addEventListener('invalid', this._handleInvalid); } disconnectedCallback() { super.disconnectedCallback(); document.removeEventListener('click', this._handleDocumentClick); this.removeEventListener('invalid', this._handleInvalid); } firstUpdated() { this._updateValidity(); if (this.value && this.fetchSelection) { this._resolveSelection(); } } updated(changedProperties) { if (changedProperties.has('required') || changedProperties.has('value')) { this._updateValidity(); } if (changedProperties.has('value')) { if (this.value) { // Only resolve if the selected option doesn't match the current value if (!this._selectedOption || this._getOptionValue(this._selectedOption) !== this.value) { this._resolveSelection(); } } else { this._selectedOption = null; } } if (changedProperties.has('options') && this._isOpen) { this._positionDropdown(); } } get form() { return this._internals.form; } get validity() { return this._internals.validity; } get validationMessage() { return this._internals.validationMessage; } checkValidity() { return this._internals.checkValidity(); } reportValidity() { return this._internals.reportValidity(); } formResetCallback() { this.value = ''; this._selectedOption = null; this._touched = false; this._isOpen = false; this._highlightedIndex = -1; this._searchQuery = ''; this._internals.setFormValue(''); this._internals.setValidity({}); } formStateRestoreCallback(state) { this.value = state; } focus() { this._triggerElement?.focus(); } blur() { this._triggerElement?.blur(); } _updateValidity() { if (this.required && !this.value) { this._internals.setValidity({ valueMissing: true }, 'Please select an option', this._triggerElement); } else { this._internals.setValidity({}); } } _handleTriggerClick() { if (this.disabled || this.readonly) { return; } if (this._isOpen) { this._close(); } else { this._open(); } } _open() { this._isOpen = true; this._searchQuery = ''; this._highlightedIndex = -1; this._fetch(); this.updateComplete.then(() => { this._positionDropdown(); if (this._searchInput) { this._searchInput.focus(); } }); } _close() { this._isOpen = false; this._highlightedIndex = -1; this._searchQuery = ''; } _positionDropdown() { requestAnimationFrame(() => { const dropdown = this.shadowRoot?.querySelector('.dropdown'); if (!dropdown) { return; } const triggerRect = this._triggerElement.getBoundingClientRect(); const spaceBelow = window.innerHeight - triggerRect.bottom - 4 - 8; const spaceAbove = triggerRect.top - 4 - 8; dropdown.style.left = triggerRect.left + 'px'; dropdown.style.width = triggerRect.width + 'px'; // Prefer opening below; only flip above when space below is tight if (spaceBelow < 200 && spaceAbove > spaceBelow) { dropdown.style.top = ''; dropdown.style.bottom = (window.innerHeight - triggerRect.top + 4) + 'px'; dropdown.style.maxHeight = spaceAbove + 'px'; } else { dropdown.style.bottom = ''; dropdown.style.top = triggerRect.bottom + 4 + 'px'; dropdown.style.maxHeight = spaceBelow + 'px'; } }); } _fetch() { if (!this.fetch) { return; } this._requestId++; const requestId = this._requestId; this.fetch(this._searchQuery).then((options) => { if (requestId === this._requestId) { this.options = options; } }).catch((error) => { console.error('kr-combo-box: fetch failed', error); }); } _handleSearchInput(e) { this._searchQuery = e.target.value; this._highlightedIndex = -1; this._fetch(); } _handleSearchKeyDown(e) { switch (e.key) { case 'ArrowDown': e.preventDefault(); this._highlightedIndex = Math.min(this._highlightedIndex + 1, this.options.length - 1); this._scrollToHighlighted(); break; case 'ArrowUp': e.preventDefault(); if (this._highlightedIndex === -1) { this._highlightedIndex = this.options.length - 1; } else { this._highlightedIndex = Math.max(this._highlightedIndex - 1, 0); } this._scrollToHighlighted(); break; case 'Enter': e.preventDefault(); if (this._highlightedIndex >= 0 && this.options[this._highlightedIndex]) { this._selectOption(this.options[this._highlightedIndex]); } break; case 'Escape': e.preventDefault(); this._close(); this._triggerElement?.focus(); break; case 'Tab': this._close(); break; } } _handleTriggerKeyDown(e) { if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if (!this._isOpen) { this._open(); } } } _handleTriggerBlur() { this._touched = true; this._updateValidity(); } _scrollToHighlighted() { this.updateComplete.then(() => { const container = this.shadowRoot?.querySelector('.options'); const highlighted = this.shadowRoot?.querySelector('.option--highlighted'); if (container && highlighted) { const containerRect = container.getBoundingClientRect(); const highlightedRect = highlighted.getBoundingClientRect(); if (highlightedRect.bottom > containerRect.bottom) { highlighted.scrollIntoView({ block: 'nearest' }); } else if (highlightedRect.top < containerRect.top) { highlighted.scrollIntoView({ block: 'nearest' }); } } }); } _getOptionValue(option) { if (typeof this.optionValue === 'function') { return this.optionValue(option); } return option[this.optionValue]; } _getOptionLabel(option) { let label; if (typeof this.optionLabel === 'function') { label = this.optionLabel(option); } else { label = option[this.optionLabel]; } if (!label) { return this._getOptionValue(option); } return label; } _resolveSelection() { if (!this.fetchSelection) { return; } const fetchedValue = this.value; this.fetchSelection(this.value).then((option) => { if (this.value !== fetchedValue) { return; } if (!option) { return; } this._selectedOption = option; this.requestUpdate(); }).catch((error) => { console.error('kr-combo-box: fetchSelection failed', error); }); } _selectOption(option) { if (option.disabled) { return; } this.value = this._getOptionValue(option); this._selectedOption = option; this._close(); this._internals.setFormValue(this.value); this._updateValidity(); this.dispatchEvent(new CustomEvent('select', { detail: { option: option }, bubbles: true, composed: true, })); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); this._triggerElement?.focus(); } _handleOptionMouseEnter(e, index) { this._highlightedIndex = index; } _renderOption(option, index) { const optionValue = this._getOptionValue(option); return html ` <button class=${classMap({ option: true, 'option--highlighted': index === this._highlightedIndex, 'option--selected': optionValue === this.value, })} type=\"button\" role=\"option\" aria-selected=${optionValue === this.value} ?disabled=${option.disabled} @click=${(e) => this._selectOption(option)} @mouseenter=${(e) => this._handleOptionMouseEnter(e, index)} > ${this._getOptionLabel(option)} </button> `; } render() { let footer = nothing; if (this._touched && this.required && !this.value) { footer = html `<div class=\"validation-message\">Please select an option</div>`; } else if (this.hint) { footer = html `<div class=\"hint\">${this.hint}</div>`; } return html ` <div class=\"wrapper\"> ${this.label ? html ` <label> ${this.label} ${this.required ? html `<span class=\"required\">*</span>` : nothing} </label> ` : nothing} <button class=${classMap({ trigger: true, 'trigger--open': this._isOpen, 'trigger--invalid': this._touched && this.required && !this.value, })} type=\"button\" ?disabled=${this.disabled} aria-haspopup=\"listbox\" aria-expanded=${this._isOpen} @click=${this._handleTriggerClick} @keydown=${this._handleTriggerKeyDown} @blur=${this._handleTriggerBlur} > <span class=${classMap({ trigger__value: true, trigger__placeholder: !this._selectedOption, })}> ${this._selectedOption ? this._getOptionLabel(this._selectedOption) : this.placeholder} </span> <svg class=${classMap({ trigger__icon: true, 'trigger__icon--open': this._isOpen, })} viewBox=\"0 0 24 24\" fill=\"currentColor\" > <path d=\"M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z\"/> </svg> </button> <div role=\"listbox\" class=${classMap({ dropdown: true, 'dropdown--open': this._isOpen, })} > <div class=\"search\"> <div class=\"search__field\"> <input class=\"search__input\" type=\"text\" placeholder=\"Search...\" .value=${this._searchQuery} @input=${this._handleSearchInput} @keydown=${this._handleSearchKeyDown} /> <svg class=\"search__icon\" viewBox=\"0 0 24 24\" fill=\"currentColor\"> <path d=\"M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z\"/> </svg> </div> </div> <div class=\"options\"> ${this.options.length === 0 ? html `<div class=\"empty\">No options found</div>` : this.options.map((option, index) => this._renderOption(option, index))} </div> </div> ${footer} </div> `; } }"
6672
+ }
6673
+ ],
6674
+ "exports": [
6675
+ {
6676
+ "kind": "js",
6677
+ "name": "KRComboBox",
6678
+ "declaration": {
6679
+ "name": "KRComboBox",
6680
+ "module": "dist/form/combo-box/combo-box.js"
6681
+ }
6682
+ }
6683
+ ]
6684
+ },
6638
6685
  {
6639
6686
  "kind": "javascript-module",
6640
6687
  "path": "dist/form/detail-field/detail-field.js",
@@ -6769,6 +6816,28 @@
6769
6816
  "static": true,
6770
6817
  "default": "true"
6771
6818
  },
6819
+ {
6820
+ "kind": "field",
6821
+ "name": "_internals",
6822
+ "type": {
6823
+ "text": "ElementInternals"
6824
+ },
6825
+ "privacy": "private"
6826
+ },
6827
+ {
6828
+ "kind": "field",
6829
+ "name": "_requestId",
6830
+ "type": {
6831
+ "text": "number"
6832
+ },
6833
+ "privacy": "private",
6834
+ "default": "0"
6835
+ },
6836
+ {
6837
+ "kind": "field",
6838
+ "name": "_handleDocumentClick",
6839
+ "privacy": "private"
6840
+ },
6772
6841
  {
6773
6842
  "kind": "field",
6774
6843
  "name": "label",
@@ -6853,71 +6922,20 @@
6853
6922
  "kind": "field",
6854
6923
  "name": "options",
6855
6924
  "type": {
6856
- "text": "SuggestionOptions"
6925
+ "text": "KRAutoSuggestOption[]"
6857
6926
  },
6858
6927
  "default": "[]",
6859
- "description": "Array of suggestion options or groups",
6928
+ "description": "Array of suggestion options",
6860
6929
  "attribute": "options"
6861
6930
  },
6862
6931
  {
6863
6932
  "kind": "field",
6864
- "name": "statusType",
6865
- "type": {
6866
- "text": "AutoSuggestStatusType"
6867
- },
6868
- "default": "'finished'",
6869
- "description": "Loading state: 'pending' | 'loading' | 'finished' | 'error'",
6870
- "attribute": "status-type"
6871
- },
6872
- {
6873
- "kind": "field",
6874
- "name": "loadingText",
6875
- "type": {
6876
- "text": "string"
6877
- },
6878
- "default": "'Loading...'",
6879
- "description": "Text to show during loading",
6880
- "attribute": "loading-text"
6881
- },
6882
- {
6883
- "kind": "field",
6884
- "name": "errorText",
6885
- "type": {
6886
- "text": "string"
6887
- },
6888
- "default": "'Error loading options'",
6889
- "description": "Text to show on error",
6890
- "attribute": "error-text"
6891
- },
6892
- {
6893
- "kind": "field",
6894
- "name": "emptyText",
6895
- "type": {
6896
- "text": "string"
6897
- },
6898
- "default": "'No matches found'",
6899
- "description": "Text to show when no matches",
6900
- "attribute": "empty-text"
6901
- },
6902
- {
6903
- "kind": "field",
6904
- "name": "filteringType",
6905
- "type": {
6906
- "text": "AutoSuggestFilteringType"
6907
- },
6908
- "default": "'auto'",
6909
- "description": "'auto' (client-side) or 'manual' (server-side)",
6910
- "attribute": "filtering-type"
6911
- },
6912
- {
6913
- "kind": "field",
6914
- "name": "highlightMatches",
6933
+ "name": "fetch",
6915
6934
  "type": {
6916
- "text": "boolean"
6935
+ "text": "Function"
6917
6936
  },
6918
- "default": "true",
6919
- "description": "Whether to highlight matching text",
6920
- "attribute": "highlight-matches"
6937
+ "default": "null",
6938
+ "description": "Function that returns a promise of options"
6921
6939
  },
6922
6940
  {
6923
6941
  "kind": "field",
@@ -6946,15 +6964,6 @@
6946
6964
  "privacy": "private",
6947
6965
  "default": "false"
6948
6966
  },
6949
- {
6950
- "kind": "field",
6951
- "name": "_dirty",
6952
- "type": {
6953
- "text": "boolean"
6954
- },
6955
- "privacy": "private",
6956
- "default": "false"
6957
- },
6958
6967
  {
6959
6968
  "kind": "field",
6960
6969
  "name": "_input",
@@ -6963,19 +6972,6 @@
6963
6972
  },
6964
6973
  "privacy": "private"
6965
6974
  },
6966
- {
6967
- "kind": "field",
6968
- "name": "_internals",
6969
- "type": {
6970
- "text": "ElementInternals"
6971
- },
6972
- "privacy": "private"
6973
- },
6974
- {
6975
- "kind": "field",
6976
- "name": "_handleDocumentClick",
6977
- "privacy": "private"
6978
- },
6979
6975
  {
6980
6976
  "kind": "field",
6981
6977
  "name": "form",
@@ -7017,25 +7013,22 @@
7017
7013
  },
7018
7014
  {
7019
7015
  "kind": "field",
7020
- "name": "_filteredOptions",
7021
- "type": {
7022
- "text": "SuggestionOptions"
7023
- },
7024
- "privacy": "private",
7025
- "readonly": true
7016
+ "name": "_handleInvalid",
7017
+ "privacy": "private"
7026
7018
  },
7027
7019
  {
7028
- "kind": "field",
7029
- "name": "_flatOptions",
7030
- "type": {
7031
- "text": "SuggestionOption[]"
7032
- },
7020
+ "kind": "method",
7021
+ "name": "_updateValidity",
7033
7022
  "privacy": "private",
7034
- "readonly": true
7023
+ "return": {
7024
+ "type": {
7025
+ "text": "void"
7026
+ }
7027
+ }
7035
7028
  },
7036
7029
  {
7037
7030
  "kind": "method",
7038
- "name": "_updateFormValue",
7031
+ "name": "_fetch",
7039
7032
  "privacy": "private",
7040
7033
  "return": {
7041
7034
  "type": {
@@ -7045,7 +7038,7 @@
7045
7038
  },
7046
7039
  {
7047
7040
  "kind": "method",
7048
- "name": "_onInput",
7041
+ "name": "_handleInput",
7049
7042
  "privacy": "private",
7050
7043
  "return": {
7051
7044
  "type": {
@@ -7063,7 +7056,17 @@
7063
7056
  },
7064
7057
  {
7065
7058
  "kind": "method",
7066
- "name": "_onFocus",
7059
+ "name": "_positionDropdown",
7060
+ "privacy": "private",
7061
+ "return": {
7062
+ "type": {
7063
+ "text": "void"
7064
+ }
7065
+ }
7066
+ },
7067
+ {
7068
+ "kind": "method",
7069
+ "name": "_handleFocus",
7067
7070
  "privacy": "private",
7068
7071
  "return": {
7069
7072
  "type": {
@@ -7073,7 +7076,7 @@
7073
7076
  },
7074
7077
  {
7075
7078
  "kind": "method",
7076
- "name": "_onBlur",
7079
+ "name": "_handleBlur",
7077
7080
  "privacy": "private",
7078
7081
  "return": {
7079
7082
  "type": {
@@ -7083,7 +7086,7 @@
7083
7086
  },
7084
7087
  {
7085
7088
  "kind": "method",
7086
- "name": "_onKeyDown",
7089
+ "name": "_handleKeyDown",
7087
7090
  "privacy": "private",
7088
7091
  "return": {
7089
7092
  "type": {
@@ -7122,7 +7125,7 @@
7122
7125
  {
7123
7126
  "name": "option",
7124
7127
  "type": {
7125
- "text": "SuggestionOption"
7128
+ "text": "KRAutoSuggestOption"
7126
7129
  }
7127
7130
  }
7128
7131
  ]
@@ -7139,7 +7142,7 @@
7139
7142
  },
7140
7143
  {
7141
7144
  "kind": "method",
7142
- "name": "_onDocumentClick",
7145
+ "name": "_handleOptionMouseEnter",
7143
7146
  "privacy": "private",
7144
7147
  "return": {
7145
7148
  "type": {
@@ -7152,18 +7155,11 @@
7152
7155
  "type": {
7153
7156
  "text": "Event"
7154
7157
  }
7155
- }
7156
- ]
7157
- },
7158
- {
7159
- "kind": "method",
7160
- "name": "_highlightMatch",
7161
- "privacy": "private",
7162
- "parameters": [
7158
+ },
7163
7159
  {
7164
- "name": "text",
7160
+ "name": "index",
7165
7161
  "type": {
7166
- "text": "string"
7162
+ "text": "number"
7167
7163
  }
7168
7164
  }
7169
7165
  ]
@@ -7176,7 +7172,7 @@
7176
7172
  {
7177
7173
  "name": "option",
7178
7174
  "type": {
7179
- "text": "SuggestionOption"
7175
+ "text": "KRAutoSuggestOption"
7180
7176
  }
7181
7177
  },
7182
7178
  {
@@ -7186,28 +7182,16 @@
7186
7182
  }
7187
7183
  }
7188
7184
  ]
7189
- },
7190
- {
7191
- "kind": "method",
7192
- "name": "_renderDropdownContent",
7193
- "privacy": "private"
7194
7185
  }
7195
7186
  ],
7196
7187
  "events": [
7197
7188
  {
7198
7189
  "name": "change",
7199
7190
  "type": {
7200
- "text": "CustomEvent"
7191
+ "text": "Event"
7201
7192
  },
7202
7193
  "description": "Fired when the input value changes"
7203
7194
  },
7204
- {
7205
- "name": "load-items",
7206
- "type": {
7207
- "text": "CustomEvent"
7208
- },
7209
- "description": "Fired when items should be loaded (manual filtering)"
7210
- },
7211
7195
  {
7212
7196
  "name": "select",
7213
7197
  "type": {
@@ -7292,65 +7276,11 @@
7292
7276
  {
7293
7277
  "name": "options",
7294
7278
  "type": {
7295
- "text": "SuggestionOptions"
7279
+ "text": "KRAutoSuggestOption[]"
7296
7280
  },
7297
7281
  "default": "[]",
7298
- "description": "Array of suggestion options or groups",
7282
+ "description": "Array of suggestion options",
7299
7283
  "fieldName": "options"
7300
- },
7301
- {
7302
- "name": "status-type",
7303
- "type": {
7304
- "text": "AutoSuggestStatusType"
7305
- },
7306
- "default": "'finished'",
7307
- "description": "Loading state: 'pending' | 'loading' | 'finished' | 'error'",
7308
- "fieldName": "statusType"
7309
- },
7310
- {
7311
- "name": "loading-text",
7312
- "type": {
7313
- "text": "string"
7314
- },
7315
- "default": "'Loading...'",
7316
- "description": "Text to show during loading",
7317
- "fieldName": "loadingText"
7318
- },
7319
- {
7320
- "name": "error-text",
7321
- "type": {
7322
- "text": "string"
7323
- },
7324
- "default": "'Error loading options'",
7325
- "description": "Text to show on error",
7326
- "fieldName": "errorText"
7327
- },
7328
- {
7329
- "name": "empty-text",
7330
- "type": {
7331
- "text": "string"
7332
- },
7333
- "default": "'No matches found'",
7334
- "description": "Text to show when no matches",
7335
- "fieldName": "emptyText"
7336
- },
7337
- {
7338
- "name": "filtering-type",
7339
- "type": {
7340
- "text": "AutoSuggestFilteringType"
7341
- },
7342
- "default": "'auto'",
7343
- "description": "'auto' (client-side) or 'manual' (server-side)",
7344
- "fieldName": "filteringType"
7345
- },
7346
- {
7347
- "name": "highlight-matches",
7348
- "type": {
7349
- "text": "boolean"
7350
- },
7351
- "default": "true",
7352
- "description": "Whether to highlight matching text",
7353
- "fieldName": "highlightMatches"
7354
7284
  }
7355
7285
  ],
7356
7286
  "superclass": {
@@ -7380,6 +7310,673 @@
7380
7310
  }
7381
7311
  ]
7382
7312
  },
7313
+ {
7314
+ "kind": "javascript-module",
7315
+ "path": "src/form/combo-box/combo-box.ts",
7316
+ "declarations": [
7317
+ {
7318
+ "kind": "class",
7319
+ "description": "",
7320
+ "name": "KRComboBox",
7321
+ "members": [
7322
+ {
7323
+ "kind": "field",
7324
+ "name": "formAssociated",
7325
+ "type": {
7326
+ "text": "boolean"
7327
+ },
7328
+ "static": true,
7329
+ "default": "true"
7330
+ },
7331
+ {
7332
+ "kind": "field",
7333
+ "name": "_internals",
7334
+ "type": {
7335
+ "text": "ElementInternals"
7336
+ },
7337
+ "privacy": "private"
7338
+ },
7339
+ {
7340
+ "kind": "field",
7341
+ "name": "_requestId",
7342
+ "type": {
7343
+ "text": "number"
7344
+ },
7345
+ "privacy": "private",
7346
+ "default": "0"
7347
+ },
7348
+ {
7349
+ "kind": "field",
7350
+ "name": "_selectedOption",
7351
+ "type": {
7352
+ "text": "any"
7353
+ },
7354
+ "privacy": "private",
7355
+ "default": "null"
7356
+ },
7357
+ {
7358
+ "kind": "field",
7359
+ "name": "_handleDocumentClick",
7360
+ "privacy": "private"
7361
+ },
7362
+ {
7363
+ "kind": "field",
7364
+ "name": "label",
7365
+ "type": {
7366
+ "text": "string"
7367
+ },
7368
+ "default": "''",
7369
+ "description": "Label text",
7370
+ "attribute": "label"
7371
+ },
7372
+ {
7373
+ "kind": "field",
7374
+ "name": "name",
7375
+ "type": {
7376
+ "text": "string"
7377
+ },
7378
+ "default": "''",
7379
+ "description": "Name for form submission",
7380
+ "attribute": "name"
7381
+ },
7382
+ {
7383
+ "kind": "field",
7384
+ "name": "value",
7385
+ "type": {
7386
+ "text": "string"
7387
+ },
7388
+ "default": "''",
7389
+ "description": "Currently selected value",
7390
+ "attribute": "value"
7391
+ },
7392
+ {
7393
+ "kind": "field",
7394
+ "name": "placeholder",
7395
+ "type": {
7396
+ "text": "string"
7397
+ },
7398
+ "default": "'Select an option'",
7399
+ "description": "Placeholder text when nothing is selected",
7400
+ "attribute": "placeholder"
7401
+ },
7402
+ {
7403
+ "kind": "field",
7404
+ "name": "disabled",
7405
+ "type": {
7406
+ "text": "boolean"
7407
+ },
7408
+ "default": "false",
7409
+ "description": "Whether the field is disabled",
7410
+ "attribute": "disabled"
7411
+ },
7412
+ {
7413
+ "kind": "field",
7414
+ "name": "readonly",
7415
+ "type": {
7416
+ "text": "boolean"
7417
+ },
7418
+ "default": "false",
7419
+ "description": "Whether the field is readonly",
7420
+ "attribute": "readonly"
7421
+ },
7422
+ {
7423
+ "kind": "field",
7424
+ "name": "required",
7425
+ "type": {
7426
+ "text": "boolean"
7427
+ },
7428
+ "default": "false",
7429
+ "description": "Whether the field is required",
7430
+ "attribute": "required"
7431
+ },
7432
+ {
7433
+ "kind": "field",
7434
+ "name": "hint",
7435
+ "type": {
7436
+ "text": "string"
7437
+ },
7438
+ "default": "''",
7439
+ "description": "Helper text displayed below the field",
7440
+ "attribute": "hint"
7441
+ },
7442
+ {
7443
+ "kind": "field",
7444
+ "name": "optionValue",
7445
+ "type": {
7446
+ "text": "string|Function"
7447
+ },
7448
+ "default": "'value'",
7449
+ "description": "Field name or function to extract the value from an option",
7450
+ "attribute": "option-value"
7451
+ },
7452
+ {
7453
+ "kind": "field",
7454
+ "name": "optionLabel",
7455
+ "type": {
7456
+ "text": "string|Function"
7457
+ },
7458
+ "default": "'label'",
7459
+ "description": "Field name or function to extract the label from an option",
7460
+ "attribute": "option-label"
7461
+ },
7462
+ {
7463
+ "kind": "field",
7464
+ "name": "options",
7465
+ "type": {
7466
+ "text": "any[]"
7467
+ },
7468
+ "default": "[]",
7469
+ "description": "Array of options",
7470
+ "attribute": "options"
7471
+ },
7472
+ {
7473
+ "kind": "field",
7474
+ "name": "fetch",
7475
+ "type": {
7476
+ "text": "Function"
7477
+ },
7478
+ "default": "null",
7479
+ "description": "Function that returns a promise of options"
7480
+ },
7481
+ {
7482
+ "kind": "field",
7483
+ "name": "fetchSelection",
7484
+ "type": {
7485
+ "text": "Function"
7486
+ },
7487
+ "default": "null",
7488
+ "description": "Function that resolves a single option by its value"
7489
+ },
7490
+ {
7491
+ "kind": "field",
7492
+ "name": "_isOpen",
7493
+ "type": {
7494
+ "text": "boolean"
7495
+ },
7496
+ "privacy": "private",
7497
+ "default": "false"
7498
+ },
7499
+ {
7500
+ "kind": "field",
7501
+ "name": "_highlightedIndex",
7502
+ "type": {
7503
+ "text": "number"
7504
+ },
7505
+ "privacy": "private",
7506
+ "default": "-1"
7507
+ },
7508
+ {
7509
+ "kind": "field",
7510
+ "name": "_touched",
7511
+ "type": {
7512
+ "text": "boolean"
7513
+ },
7514
+ "privacy": "private",
7515
+ "default": "false"
7516
+ },
7517
+ {
7518
+ "kind": "field",
7519
+ "name": "_searchQuery",
7520
+ "type": {
7521
+ "text": "string"
7522
+ },
7523
+ "privacy": "private",
7524
+ "default": "''"
7525
+ },
7526
+ {
7527
+ "kind": "field",
7528
+ "name": "_triggerElement",
7529
+ "type": {
7530
+ "text": "HTMLButtonElement"
7531
+ },
7532
+ "privacy": "private"
7533
+ },
7534
+ {
7535
+ "kind": "field",
7536
+ "name": "_searchInput",
7537
+ "type": {
7538
+ "text": "HTMLInputElement"
7539
+ },
7540
+ "privacy": "private"
7541
+ },
7542
+ {
7543
+ "kind": "field",
7544
+ "name": "form",
7545
+ "readonly": true
7546
+ },
7547
+ {
7548
+ "kind": "field",
7549
+ "name": "validity",
7550
+ "readonly": true
7551
+ },
7552
+ {
7553
+ "kind": "field",
7554
+ "name": "validationMessage",
7555
+ "readonly": true
7556
+ },
7557
+ {
7558
+ "kind": "method",
7559
+ "name": "checkValidity"
7560
+ },
7561
+ {
7562
+ "kind": "method",
7563
+ "name": "reportValidity"
7564
+ },
7565
+ {
7566
+ "kind": "method",
7567
+ "name": "formResetCallback"
7568
+ },
7569
+ {
7570
+ "kind": "method",
7571
+ "name": "formStateRestoreCallback",
7572
+ "parameters": [
7573
+ {
7574
+ "name": "state",
7575
+ "type": {
7576
+ "text": "string"
7577
+ }
7578
+ }
7579
+ ]
7580
+ },
7581
+ {
7582
+ "kind": "method",
7583
+ "name": "focus"
7584
+ },
7585
+ {
7586
+ "kind": "method",
7587
+ "name": "blur"
7588
+ },
7589
+ {
7590
+ "kind": "field",
7591
+ "name": "_handleInvalid",
7592
+ "privacy": "private"
7593
+ },
7594
+ {
7595
+ "kind": "method",
7596
+ "name": "_updateValidity",
7597
+ "privacy": "private",
7598
+ "return": {
7599
+ "type": {
7600
+ "text": "void"
7601
+ }
7602
+ }
7603
+ },
7604
+ {
7605
+ "kind": "method",
7606
+ "name": "_handleTriggerClick",
7607
+ "privacy": "private",
7608
+ "return": {
7609
+ "type": {
7610
+ "text": "void"
7611
+ }
7612
+ }
7613
+ },
7614
+ {
7615
+ "kind": "method",
7616
+ "name": "_open",
7617
+ "privacy": "private",
7618
+ "return": {
7619
+ "type": {
7620
+ "text": "void"
7621
+ }
7622
+ }
7623
+ },
7624
+ {
7625
+ "kind": "method",
7626
+ "name": "_close",
7627
+ "privacy": "private",
7628
+ "return": {
7629
+ "type": {
7630
+ "text": "void"
7631
+ }
7632
+ }
7633
+ },
7634
+ {
7635
+ "kind": "method",
7636
+ "name": "_positionDropdown",
7637
+ "privacy": "private",
7638
+ "return": {
7639
+ "type": {
7640
+ "text": "void"
7641
+ }
7642
+ }
7643
+ },
7644
+ {
7645
+ "kind": "method",
7646
+ "name": "_fetch",
7647
+ "privacy": "private",
7648
+ "return": {
7649
+ "type": {
7650
+ "text": "void"
7651
+ }
7652
+ }
7653
+ },
7654
+ {
7655
+ "kind": "method",
7656
+ "name": "_handleSearchInput",
7657
+ "privacy": "private",
7658
+ "return": {
7659
+ "type": {
7660
+ "text": "void"
7661
+ }
7662
+ },
7663
+ "parameters": [
7664
+ {
7665
+ "name": "e",
7666
+ "type": {
7667
+ "text": "Event"
7668
+ }
7669
+ }
7670
+ ]
7671
+ },
7672
+ {
7673
+ "kind": "method",
7674
+ "name": "_handleSearchKeyDown",
7675
+ "privacy": "private",
7676
+ "return": {
7677
+ "type": {
7678
+ "text": "void"
7679
+ }
7680
+ },
7681
+ "parameters": [
7682
+ {
7683
+ "name": "e",
7684
+ "type": {
7685
+ "text": "KeyboardEvent"
7686
+ }
7687
+ }
7688
+ ]
7689
+ },
7690
+ {
7691
+ "kind": "method",
7692
+ "name": "_handleTriggerKeyDown",
7693
+ "privacy": "private",
7694
+ "return": {
7695
+ "type": {
7696
+ "text": "void"
7697
+ }
7698
+ },
7699
+ "parameters": [
7700
+ {
7701
+ "name": "e",
7702
+ "type": {
7703
+ "text": "KeyboardEvent"
7704
+ }
7705
+ }
7706
+ ]
7707
+ },
7708
+ {
7709
+ "kind": "method",
7710
+ "name": "_handleTriggerBlur",
7711
+ "privacy": "private",
7712
+ "return": {
7713
+ "type": {
7714
+ "text": "void"
7715
+ }
7716
+ }
7717
+ },
7718
+ {
7719
+ "kind": "method",
7720
+ "name": "_scrollToHighlighted",
7721
+ "privacy": "private",
7722
+ "return": {
7723
+ "type": {
7724
+ "text": "void"
7725
+ }
7726
+ }
7727
+ },
7728
+ {
7729
+ "kind": "method",
7730
+ "name": "_getOptionValue",
7731
+ "privacy": "private",
7732
+ "return": {
7733
+ "type": {
7734
+ "text": "string"
7735
+ }
7736
+ },
7737
+ "parameters": [
7738
+ {
7739
+ "name": "option",
7740
+ "type": {
7741
+ "text": "any"
7742
+ }
7743
+ }
7744
+ ]
7745
+ },
7746
+ {
7747
+ "kind": "method",
7748
+ "name": "_getOptionLabel",
7749
+ "privacy": "private",
7750
+ "return": {
7751
+ "type": {
7752
+ "text": "string"
7753
+ }
7754
+ },
7755
+ "parameters": [
7756
+ {
7757
+ "name": "option",
7758
+ "type": {
7759
+ "text": "any"
7760
+ }
7761
+ }
7762
+ ]
7763
+ },
7764
+ {
7765
+ "kind": "method",
7766
+ "name": "_resolveSelection",
7767
+ "privacy": "private",
7768
+ "return": {
7769
+ "type": {
7770
+ "text": "void"
7771
+ }
7772
+ }
7773
+ },
7774
+ {
7775
+ "kind": "method",
7776
+ "name": "_selectOption",
7777
+ "privacy": "private",
7778
+ "return": {
7779
+ "type": {
7780
+ "text": "void"
7781
+ }
7782
+ },
7783
+ "parameters": [
7784
+ {
7785
+ "name": "option",
7786
+ "type": {
7787
+ "text": "any"
7788
+ }
7789
+ }
7790
+ ]
7791
+ },
7792
+ {
7793
+ "kind": "method",
7794
+ "name": "_handleOptionMouseEnter",
7795
+ "privacy": "private",
7796
+ "return": {
7797
+ "type": {
7798
+ "text": "void"
7799
+ }
7800
+ },
7801
+ "parameters": [
7802
+ {
7803
+ "name": "e",
7804
+ "type": {
7805
+ "text": "Event"
7806
+ }
7807
+ },
7808
+ {
7809
+ "name": "index",
7810
+ "type": {
7811
+ "text": "number"
7812
+ }
7813
+ }
7814
+ ]
7815
+ },
7816
+ {
7817
+ "kind": "method",
7818
+ "name": "_renderOption",
7819
+ "privacy": "private",
7820
+ "parameters": [
7821
+ {
7822
+ "name": "option",
7823
+ "type": {
7824
+ "text": "any"
7825
+ }
7826
+ },
7827
+ {
7828
+ "name": "index",
7829
+ "type": {
7830
+ "text": "number"
7831
+ }
7832
+ }
7833
+ ]
7834
+ }
7835
+ ],
7836
+ "events": [
7837
+ {
7838
+ "name": "select",
7839
+ "type": {
7840
+ "text": "CustomEvent"
7841
+ },
7842
+ "description": "Fired when an option is selected"
7843
+ },
7844
+ {
7845
+ "name": "change",
7846
+ "type": {
7847
+ "text": "Event"
7848
+ },
7849
+ "description": "Fired when the selected value changes"
7850
+ }
7851
+ ],
7852
+ "attributes": [
7853
+ {
7854
+ "name": "label",
7855
+ "type": {
7856
+ "text": "string"
7857
+ },
7858
+ "default": "''",
7859
+ "description": "Label text",
7860
+ "fieldName": "label"
7861
+ },
7862
+ {
7863
+ "name": "name",
7864
+ "type": {
7865
+ "text": "string"
7866
+ },
7867
+ "default": "''",
7868
+ "description": "Name for form submission",
7869
+ "fieldName": "name"
7870
+ },
7871
+ {
7872
+ "name": "value",
7873
+ "type": {
7874
+ "text": "string"
7875
+ },
7876
+ "default": "''",
7877
+ "description": "Currently selected value",
7878
+ "fieldName": "value"
7879
+ },
7880
+ {
7881
+ "name": "placeholder",
7882
+ "type": {
7883
+ "text": "string"
7884
+ },
7885
+ "default": "'Select an option'",
7886
+ "description": "Placeholder text when nothing is selected",
7887
+ "fieldName": "placeholder"
7888
+ },
7889
+ {
7890
+ "name": "disabled",
7891
+ "type": {
7892
+ "text": "boolean"
7893
+ },
7894
+ "default": "false",
7895
+ "description": "Whether the field is disabled",
7896
+ "fieldName": "disabled"
7897
+ },
7898
+ {
7899
+ "name": "readonly",
7900
+ "type": {
7901
+ "text": "boolean"
7902
+ },
7903
+ "default": "false",
7904
+ "description": "Whether the field is readonly",
7905
+ "fieldName": "readonly"
7906
+ },
7907
+ {
7908
+ "name": "required",
7909
+ "type": {
7910
+ "text": "boolean"
7911
+ },
7912
+ "default": "false",
7913
+ "description": "Whether the field is required",
7914
+ "fieldName": "required"
7915
+ },
7916
+ {
7917
+ "name": "hint",
7918
+ "type": {
7919
+ "text": "string"
7920
+ },
7921
+ "default": "''",
7922
+ "description": "Helper text displayed below the field",
7923
+ "fieldName": "hint"
7924
+ },
7925
+ {
7926
+ "name": "option-value",
7927
+ "type": {
7928
+ "text": "string|Function"
7929
+ },
7930
+ "default": "'value'",
7931
+ "description": "Field name or function to extract the value from an option",
7932
+ "fieldName": "optionValue"
7933
+ },
7934
+ {
7935
+ "name": "option-label",
7936
+ "type": {
7937
+ "text": "string|Function"
7938
+ },
7939
+ "default": "'label'",
7940
+ "description": "Field name or function to extract the label from an option",
7941
+ "fieldName": "optionLabel"
7942
+ },
7943
+ {
7944
+ "name": "options",
7945
+ "type": {
7946
+ "text": "any[]"
7947
+ },
7948
+ "default": "[]",
7949
+ "description": "Array of options",
7950
+ "fieldName": "options"
7951
+ }
7952
+ ],
7953
+ "superclass": {
7954
+ "name": "LitElement",
7955
+ "package": "lit"
7956
+ },
7957
+ "tagName": "kr-combo-box",
7958
+ "customElement": true
7959
+ }
7960
+ ],
7961
+ "exports": [
7962
+ {
7963
+ "kind": "js",
7964
+ "name": "KRComboBox",
7965
+ "declaration": {
7966
+ "name": "KRComboBox",
7967
+ "module": "src/form/combo-box/combo-box.ts"
7968
+ }
7969
+ },
7970
+ {
7971
+ "kind": "custom-element-definition",
7972
+ "name": "kr-combo-box",
7973
+ "declaration": {
7974
+ "name": "KRComboBox",
7975
+ "module": "src/form/combo-box/combo-box.ts"
7976
+ }
7977
+ }
7978
+ ]
7979
+ },
7383
7980
  {
7384
7981
  "kind": "javascript-module",
7385
7982
  "path": "src/form/detail-field/detail-field.ts",