@kodaris/krubble-components 1.0.11 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/custom-elements.json +137 -175
  2. package/dist/button/button.d.ts +1 -1
  3. package/dist/button/button.d.ts.map +1 -1
  4. package/dist/button/button.js +21 -0
  5. package/dist/button/button.js.map +1 -1
  6. package/dist/form/index.d.ts +2 -2
  7. package/dist/form/index.d.ts.map +1 -1
  8. package/dist/form/index.js +2 -2
  9. package/dist/form/index.js.map +1 -1
  10. package/dist/form/{select/select-option.d.ts → select-field/select-field-option.d.ts} +5 -5
  11. package/dist/form/select-field/select-field-option.d.ts.map +1 -0
  12. package/dist/form/{select/select-option.js → select-field/select-field-option.js} +11 -11
  13. package/dist/form/select-field/select-field-option.js.map +1 -0
  14. package/dist/form/{select/select.d.ts → select-field/select-field.d.ts} +5 -5
  15. package/dist/form/select-field/select-field.d.ts.map +1 -0
  16. package/dist/form/{select/select.js → select-field/select-field.js} +33 -33
  17. package/dist/form/select-field/select-field.js.map +1 -0
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/krubble.bundled.js +81 -105
  23. package/dist/krubble.bundled.js.map +1 -1
  24. package/dist/krubble.bundled.min.js +42 -17
  25. package/dist/krubble.bundled.min.js.map +1 -1
  26. package/dist/krubble.umd.js +80 -104
  27. package/dist/krubble.umd.js.map +1 -1
  28. package/dist/krubble.umd.min.js +86 -61
  29. package/dist/krubble.umd.min.js.map +1 -1
  30. package/dist/table/table.d.ts +1 -4
  31. package/dist/table/table.d.ts.map +1 -1
  32. package/dist/table/table.js +19 -64
  33. package/dist/table/table.js.map +1 -1
  34. package/package.json +1 -1
  35. package/dist/form/select/select-option.d.ts.map +0 -1
  36. package/dist/form/select/select-option.js.map +0 -1
  37. package/dist/form/select/select.d.ts.map +0 -1
  38. package/dist/form/select/select.js.map +0 -1
@@ -113,17 +113,17 @@
113
113
  },
114
114
  {
115
115
  "kind": "js",
116
- "name": "KRSelect",
116
+ "name": "KRSelectField",
117
117
  "declaration": {
118
- "name": "KRSelect",
118
+ "name": "KRSelectField",
119
119
  "module": "./form/index.js"
120
120
  }
121
121
  },
122
122
  {
123
123
  "kind": "js",
124
- "name": "KRSelectOption",
124
+ "name": "KRSelectFieldOption",
125
125
  "declaration": {
126
- "name": "KRSelectOption",
126
+ "name": "KRSelectFieldOption",
127
127
  "module": "./form/index.js"
128
128
  }
129
129
  }
@@ -256,7 +256,7 @@
256
256
  {
257
257
  "kind": "variable",
258
258
  "name": "KRTable",
259
- "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._displayedColumns = []; this._widthsLocked = false; this._resizing = null; this._resizeObserver = null; this._searchPositionLocked = false; this._def = { columns: [] }; this.def = { columns: [] }; this._handleClickOutsideColumnPicker = (e) => { if (!this._columnPickerOpen) return; const path = e.composedPath(); const picker = this.shadowRoot?.querySelector('.column-picker-wrapper'); if (picker && !path.includes(picker)) { this._columnPickerOpen = false; } }; this._handleResizeMove = (e) => { if (!this._resizing) return; const col = this._def.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._handleClickOutsideColumnPicker); 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._handleClickOutsideColumnPicker); this._resizeObserver?.disconnect(); } willUpdate(changedProperties) { if (changedProperties.has('def')) { // Copy user's def and normalize action columns this._def = { ...this.def, columns: this.def.columns.map(col => { if (col.type === 'actions') { return { ...col, sticky: 'right', resizable: false }; } return { ...col }; }) }; this._displayedColumns = this._def.displayedColumns || this._def.columns.map(c => c.id); this._widthsLocked = false; this.classList.remove('kr-table--widths-locked'); this._fetch(); this._initRefresh(); } } updated(changedProperties) { this._updateScrollFlags(); } // ---------------------------------------------------------------------------- // 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 // ---------------------------------------------------------------------------- /** * 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._def.dataSource) return; this._dataState = 'loading'; // Build request based on mode let request; switch (this._def.dataSource.mode) { case 'opensearch': throw Error('Opensearch not supported yet'); case 'db': throw Error('DB not supported yet'); default: // solr request = { page: this._page - 1, size: this._pageSize, sorts: [], filterFields: [], queryFields: [], facetFields: [] }; if (this._searchQuery?.trim().length) { request.queryFields.push({ name: '_text_', operation: 'IS', value: escapeSolrQuery(this._searchQuery) }); } } this._def.dataSource.fetch(request) .then(response => { // Parse response based on mode switch (this._def.dataSource?.mode) { case 'opensearch': { throw Error('Opensearch not supported yet'); } case 'db': { throw Error('DB not supported yet'); } 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._dataState = 'success'; this._updateSearchPosition(); if (!this._widthsLocked) this._lockColumnWidths(); }) .catch(err => { this._dataState = 'error'; KRSnackbar.show({ message: err instanceof Error ? err.message : 'Failed to load data', type: 'error' }); }); } /** * 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._def.refreshInterval && this._def.refreshInterval > 0) { this._refreshTimer = window.setInterval(() => { this._fetch(); }, this._def.refreshInterval); } } _handleSearch(e) { const input = e.target; this._searchQuery = input.value; this._page = 1; this._fetch(); } _measureTextWidth(text, font) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.font = font; return ctx.measureText(text).width; } _lockColumnWidths() { this.updateComplete.then(() => { requestAnimationFrame(() => { const headerCell = this.shadowRoot?.querySelector('.header-cell'); const dataCell = this.shadowRoot?.querySelector('.cell'); if (!headerCell) return; const headerStyle = getComputedStyle(headerCell); const headerFont = `${headerStyle.fontWeight} ${headerStyle.fontSize} ${headerStyle.fontFamily}`; const headerPadding = parseFloat(headerStyle.paddingLeft) + parseFloat(headerStyle.paddingRight); const dataStyle = dataCell ? getComputedStyle(dataCell) : headerStyle; const dataFont = `${dataStyle.fontWeight} ${dataStyle.fontSize} ${dataStyle.fontFamily}`; const dataPadding = dataCell ? parseFloat(dataStyle.paddingLeft) + parseFloat(dataStyle.paddingRight) : headerPadding; this.getDisplayedColumns().forEach(col => { if (!col.width) { let width; // For columns with custom render functions, measure the actual DOM element if (col.render) { const cell = this.shadowRoot?.querySelector(`.cell[data-column-id=\"${col.id}\"]`); width = cell ? cell.scrollWidth : 150; } else { const headerText = col.label ?? col.id; const headerWidth = this._measureTextWidth(headerText, headerFont) + headerPadding; let maxDataWidth = 0; for (const row of this._data) { const value = row[col.id]; if (value != null) { const dataWidth = this._measureTextWidth(String(value), dataFont) + dataPadding; if (dataWidth > maxDataWidth) maxDataWidth = dataWidth; } } width = Math.ceil(Math.max(headerWidth, maxDataWidth)); } col.width = `${Math.max(width, 150)}px`; } }); this._widthsLocked = true; this.classList.add('kr-table--widths-locked'); this.requestUpdate(); }); }); } /** * 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._displayedColumns.includes(columnId)) { this._displayedColumns = this._displayedColumns.filter(id => id !== columnId); } else { this._displayedColumns = [...this._displayedColumns, columnId]; } } // 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._displayedColumns .map(id => this._def.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) { this.dispatchEvent(new CustomEvent('action', { detail: { action: action.id }, bubbles: true, composed: true })); } // ---------------------------------------------------------------------------- // Rendering // ---------------------------------------------------------------------------- _renderCellContent(column, row) { const value = row[column.id]; if (column.render) { const result = column.render(row); // If render returns a string, treat it as HTML return typeof result === 'string' ? o$2(result) : result; } if (value === null || value === undefined) { return ''; } switch (column.type) { case 'number': return typeof value === 'number' ? value.toLocaleString() : String(value); case 'currency': return typeof value === 'number' ? value.toLocaleString('en-US', { style: 'currency', currency: 'USD' }) : String(value); case 'date': return value instanceof Date ? value.toLocaleDateString() : new Date(value).toLocaleDateString(); 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--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._def.title && !this._def.actions?.length && this._totalPages <= 1) { return A; } return b ` <div class=\"header\"> <div class=\"title\">${this._def.title ?? ''}</div> <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._def.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._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._def.actions?.length === 1 ? b ` <kr-button class=\"actions\" @click=${() => this._handleAction(this._def.actions[0])} > ${this._def.actions[0].label} </kr-button> ` : this._def.actions?.length ? b ` <kr-button class=\"actions\" .options=${this._def.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; } _getGridTemplateColumns() { const cols = this.getDisplayedColumns(); const lastNonStickyIndex = cols.map((c, i) => c.sticky ? -1 : i).filter(i => i >= 0).pop(); return cols.map((col, i) => { if (i === lastNonStickyIndex && this._widthsLocked) { return `minmax(${col.width || 'auto'}, 1fr)`; } return col.width || 'auto'; }).join(' '); } /** 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._data.map(row => b ` <div class=\"row\"> ${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)} </div> `)} </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._def.columns.length) { return b `<slot></slot>`; } return b ` ${this._renderHeader()} ${this._renderTable()} `; } }"
259
+ "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._displayedColumns = []; this._resizing = null; this._resizeObserver = null; this._searchPositionLocked = false; this._def = { columns: [] }; this.def = { columns: [] }; this._handleClickOutsideColumnPicker = (e) => { if (!this._columnPickerOpen) return; const path = e.composedPath(); const picker = this.shadowRoot?.querySelector('.column-picker-wrapper'); if (picker && !path.includes(picker)) { this._columnPickerOpen = false; } }; this._handleResizeMove = (e) => { if (!this._resizing) return; const col = this._def.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._handleClickOutsideColumnPicker); 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._handleClickOutsideColumnPicker); this._resizeObserver?.disconnect(); } willUpdate(changedProperties) { if (changedProperties.has('def')) { // Copy user's def and normalize action columns this._def = { ...this.def, columns: this.def.columns.map(col => { if (col.type === 'actions') { return { ...col, sticky: 'right', resizable: false }; } return { ...col }; }) }; this._displayedColumns = this._def.displayedColumns || this._def.columns.map(c => c.id); this._fetch(); this._initRefresh(); } } updated(changedProperties) { this._updateScrollFlags(); } // ---------------------------------------------------------------------------- // 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 // ---------------------------------------------------------------------------- /** * 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._def.dataSource) return; this._dataState = 'loading'; // Build request based on mode let request; switch (this._def.dataSource.mode) { case 'opensearch': throw Error('Opensearch not supported yet'); case 'db': throw Error('DB not supported yet'); default: // solr request = { page: this._page - 1, size: this._pageSize, sorts: [], filterFields: [], queryFields: [], facetFields: [] }; if (this._searchQuery?.trim().length) { request.queryFields.push({ name: '_text_', operation: 'IS', value: escapeSolrQuery(this._searchQuery) }); } } this._def.dataSource.fetch(request) .then(response => { // Parse response based on mode switch (this._def.dataSource?.mode) { case 'opensearch': { throw Error('Opensearch not supported yet'); } case 'db': { throw Error('DB not supported yet'); } 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._dataState = 'success'; this._updateSearchPosition(); }) .catch(err => { this._dataState = 'error'; KRSnackbar.show({ message: err instanceof Error ? err.message : 'Failed to load data', type: 'error' }); }); } /** * 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._def.refreshInterval && this._def.refreshInterval > 0) { this._refreshTimer = window.setInterval(() => { this._fetch(); }, this._def.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._displayedColumns.includes(columnId)) { this._displayedColumns = this._displayedColumns.filter(id => id !== columnId); } else { this._displayedColumns = [...this._displayedColumns, columnId]; } } // 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._displayedColumns .map(id => this._def.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) { this.dispatchEvent(new CustomEvent('action', { detail: { action: action.id }, bubbles: true, composed: true })); } // ---------------------------------------------------------------------------- // Rendering // ---------------------------------------------------------------------------- _renderCellContent(column, row) { const value = row[column.id]; if (column.render) { const result = column.render(row); // If render returns a string, treat it as HTML return typeof result === 'string' ? o$2(result) : result; } if (value === null || value === undefined) { return ''; } switch (column.type) { case 'number': return typeof value === 'number' ? value.toLocaleString() : String(value); case 'currency': return typeof value === 'number' ? value.toLocaleString('en-US', { style: 'currency', currency: 'USD' }) : String(value); case 'date': return value instanceof Date ? value.toLocaleDateString() : new Date(value).toLocaleDateString(); 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._def.title && !this._def.actions?.length && this._totalPages <= 1) { return A; } return b ` <div class=\"header\"> <div class=\"title\">${this._def.title ?? ''}</div> <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._def.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._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._def.actions?.length === 1 ? b ` <kr-button class=\"actions\" @click=${() => this._handleAction(this._def.actions[0])} > ${this._def.actions[0].label} </kr-button> ` : this._def.actions?.length ? b ` <kr-button class=\"actions\" .options=${this._def.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; } /** 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._data.map(row => b ` <div class=\"row\"> ${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)} </div> `)} </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._def.columns.length) { return b `<slot></slot>`; } return b ` ${this._renderHeader()} ${this._renderTable()} `; } }"
260
260
  },
261
261
  {
262
262
  "kind": "variable",
@@ -266,15 +266,15 @@
266
266
  },
267
267
  {
268
268
  "kind": "variable",
269
- "name": "KRSelect",
270
- "default": "class KRSelect extends i$2 { constructor() { super(); /** * The select label text */ this.label = ''; /** * The input name for form submission */ this.name = ''; /** * The currently selected value */ this.value = ''; /** * Placeholder text when no option is selected */ this.placeholder = 'Select an option'; /** * Whether the select is disabled */ this.disabled = false; /** * Whether the field is required */ this.required = false; /** * Whether the field is readonly */ this.readonly = false; /** * Helper text shown below the select */ this.hint = ''; this._isOpen = false; this._highlightedIndex = -1; this._touched = false; this._handleInvalid = (e) => { e.preventDefault(); this._touched = true; }; this._handleOutsideClick = (e) => { if (!this.contains(e.target)) { this._close(); } }; this._handleKeyDown = (e) => { if (!this._isOpen) return; const options = Array.from(this.querySelectorAll('kr-select-option')); switch (e.key) { case 'Escape': this._close(); this._triggerElement?.focus(); break; case 'ArrowDown': e.preventDefault(); if (options.some(o => !o.disabled)) { let newIndex = this._highlightedIndex + 1; while (newIndex < options.length && options[newIndex]?.disabled) newIndex++; if (newIndex < options.length) this._highlightedIndex = newIndex; } break; case 'ArrowUp': e.preventDefault(); { let newIndex = this._highlightedIndex - 1; while (newIndex >= 0 && options[newIndex]?.disabled) newIndex--; if (newIndex >= 0) this._highlightedIndex = newIndex; } break; case 'Enter': e.preventDefault(); if (this._highlightedIndex >= 0 && this._highlightedIndex < options.length) { this._selectOption(options[this._highlightedIndex]); } break; } }; this._internals = this.attachInternals(); } // Form-associated custom element callbacks 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 = false; this._internals.setFormValue(''); this._internals.setValidity({}); } formStateRestoreCallback(state) { this.value = state; } connectedCallback() { super.connectedCallback(); document.addEventListener('click', this._handleOutsideClick); document.addEventListener('keydown', this._handleKeyDown); this.addEventListener('invalid', this._handleInvalid); } firstUpdated() { this._updateValidity(); } updated(changedProperties) { if (changedProperties.has('required') || changedProperties.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) return; if (this._isOpen) { this._close(); } else { this._isOpen = true; const options = Array.from(this.querySelectorAll('kr-select-option')); this._highlightedIndex = options.findIndex(o => o.value === this.value); } } _close() { this._isOpen = false; this._highlightedIndex = -1; } _selectOption(option) { if (option.disabled) return; this.value = option.value; this._internals.setFormValue(this.value); this._updateValidity(); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); this._close(); this._triggerElement?.focus(); } _handleBlur() { this._touched = true; this._updateValidity(); } _updateValidity() { if (this.required && !this.value) { this._internals.setValidity({ valueMissing: true }, 'Please select an option', this._triggerElement); } else { this._internals.setValidity({}); } } render() { const options = Array.from(this.querySelectorAll('kr-select-option')); const selectedLabel = options.find(o => o.value === this.value)?.label; return b ` <div class=\"wrapper\"> ${this.label ? b ` <label> ${this.label} ${this.required ? b `<span class=\"required\" aria-hidden=\"true\">*</span>` : ''} </label> ` : A} <div class=\"select-wrapper\"> <button class=${e$1({ 'select-trigger': true, '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=${e$1({ 'select-value': true, 'select-placeholder': !selectedLabel })}> ${selectedLabel || this.placeholder} </span> <svg class=${e$1({ 'chevron-icon': true, 'select-icon': true, 'select-icon--open': this._isOpen })} viewBox=\"0 0 20 20\" fill=\"currentColor\" > <path fill-rule=\"evenodd\" d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\" clip-rule=\"evenodd\" /> </svg> </button> <div class=${e$1({ 'select-dropdown': true, 'hidden': !this._isOpen })} role=\"listbox\"> <div class=\"select-options\"> ${options.length === 0 ? b `<div class=\"select-empty\">No options available</div>` : options.map((option, idx) => { const isSelected = option.value === this.value; return b ` <div class=${e$1({ 'select-option': true, 'select-option--selected': isSelected, 'select-option--disabled': option.disabled, 'select-option--highlighted': idx === this._highlightedIndex, })} role=\"option\" aria-selected=${isSelected} @click=${() => this._selectOption(option)} @mouseenter=${() => (this._highlightedIndex = idx)} > ${option.label} ${isSelected ? b `<svg class=\"chevron-icon select-check\" viewBox=\"0 0 20 20\" fill=\"currentColor\"> <path fill-rule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clip-rule=\"evenodd\" /> </svg>` : A} </div> `; })} </div> </div> </div> ${this._touched && this.required && !this.value ? b `<div class=\"validation-message\">Please select an option</div>` : this.hint ? b `<div class=\"hint\">${this.hint}</div>` : ''} </div> <div class=\"options-slot\"> <slot @slotchange=${() => this.requestUpdate()}></slot> </div> `; } // Public methods for programmatic control focus() { this._triggerElement?.focus(); } blur() { this._triggerElement?.blur(); } }",
269
+ "name": "KRSelectField",
270
+ "default": "class KRSelectField extends i$2 { constructor() { super(); /** * The select label text */ this.label = ''; /** * The input name for form submission */ this.name = ''; /** * The currently selected value */ this.value = ''; /** * Placeholder text when no option is selected */ this.placeholder = 'Select an option'; /** * Whether the select is disabled */ this.disabled = false; /** * Whether the field is required */ this.required = false; /** * Whether the field is readonly */ this.readonly = false; /** * Helper text shown below the select */ this.hint = ''; this._isOpen = false; this._highlightedIndex = -1; this._touched = false; this._handleInvalid = (e) => { e.preventDefault(); this._touched = true; }; this._handleOutsideClick = (e) => { if (!e.composedPath().includes(this)) { this._close(); } }; this._handleKeyDown = (e) => { if (!this._isOpen) return; const options = Array.from(this.querySelectorAll('kr-select-field-option')); switch (e.key) { case 'Escape': this._close(); this._triggerElement?.focus(); break; case 'ArrowDown': e.preventDefault(); if (options.some(o => !o.disabled)) { let newIndex = this._highlightedIndex + 1; while (newIndex < options.length && options[newIndex]?.disabled) newIndex++; if (newIndex < options.length) this._highlightedIndex = newIndex; } break; case 'ArrowUp': e.preventDefault(); { let newIndex = this._highlightedIndex - 1; while (newIndex >= 0 && options[newIndex]?.disabled) newIndex--; if (newIndex >= 0) this._highlightedIndex = newIndex; } break; case 'Enter': e.preventDefault(); if (this._highlightedIndex >= 0 && this._highlightedIndex < options.length) { this._selectOption(options[this._highlightedIndex]); } break; } }; this._internals = this.attachInternals(); } // Form-associated custom element callbacks 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 = false; this._internals.setFormValue(''); this._internals.setValidity({}); } formStateRestoreCallback(state) { this.value = state; } connectedCallback() { super.connectedCallback(); document.addEventListener('click', this._handleOutsideClick); document.addEventListener('keydown', this._handleKeyDown); this.addEventListener('invalid', this._handleInvalid); } firstUpdated() { this._updateValidity(); } updated(changedProperties) { if (changedProperties.has('required') || changedProperties.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) return; if (this._isOpen) { this._close(); } else { this._isOpen = true; const options = Array.from(this.querySelectorAll('kr-select-field-option')); this._highlightedIndex = options.findIndex(o => o.value === this.value); } } _close() { this._isOpen = false; this._highlightedIndex = -1; } _selectOption(option) { if (option.disabled) return; this.value = option.value; this._internals.setFormValue(this.value); this._updateValidity(); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); this._close(); this._triggerElement?.focus(); } _handleBlur() { this._touched = true; this._updateValidity(); } _updateValidity() { if (this.required && !this.value) { this._internals.setValidity({ valueMissing: true }, 'Please select an option', this._triggerElement); } else { this._internals.setValidity({}); } } render() { const options = Array.from(this.querySelectorAll('kr-select-field-option')); const selectedLabel = options.find(o => o.value === this.value)?.label; return b ` <div class=\"wrapper\"> ${this.label ? b ` <label> ${this.label} ${this.required ? b `<span class=\"required\" aria-hidden=\"true\">*</span>` : ''} </label> ` : A} <div class=\"select-wrapper\"> <button class=${e$1({ 'select-trigger': true, '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=${e$1({ 'select-value': true, 'select-placeholder': !selectedLabel })}> ${selectedLabel || this.placeholder} </span> <svg class=${e$1({ 'chevron-icon': true, 'select-icon': true, 'select-icon--open': this._isOpen })} viewBox=\"0 0 20 20\" fill=\"currentColor\" > <path fill-rule=\"evenodd\" d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\" clip-rule=\"evenodd\" /> </svg> </button> <div class=${e$1({ 'select-dropdown': true, 'hidden': !this._isOpen })} role=\"listbox\"> <div class=\"select-options\"> ${options.length === 0 ? b `<div class=\"select-empty\">No options available</div>` : options.map((option, idx) => { const isSelected = option.value === this.value; return b ` <div class=${e$1({ 'select-option': true, 'select-option--selected': isSelected, 'select-option--disabled': option.disabled, 'select-option--highlighted': idx === this._highlightedIndex, })} role=\"option\" aria-selected=${isSelected} @click=${() => this._selectOption(option)} @mouseenter=${() => (this._highlightedIndex = idx)} > ${option.label} ${isSelected ? b `<svg class=\"chevron-icon select-check\" viewBox=\"0 0 20 20\" fill=\"currentColor\"> <path fill-rule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clip-rule=\"evenodd\" /> </svg>` : A} </div> `; })} </div> </div> </div> ${this._touched && this.required && !this.value ? b `<div class=\"validation-message\">Please select an option</div>` : this.hint ? b `<div class=\"hint\">${this.hint}</div>` : ''} </div> <div class=\"options-slot\"> <slot @slotchange=${() => this.requestUpdate()}></slot> </div> `; } // Public methods for programmatic control focus() { this._triggerElement?.focus(); } blur() { this._triggerElement?.blur(); } }",
271
271
  "description": "A select dropdown component that works with native browser forms.\n\nUses ElementInternals for form association, allowing the component\nto participate in form submission, validation, and reset."
272
272
  },
273
273
  {
274
274
  "kind": "variable",
275
- "name": "KRSelectOption",
276
- "default": "class KRSelectOption extends i$2 { constructor() { super(...arguments); /** * The option value */ this.value = ''; /** * Whether the option is disabled */ this.disabled = false; } /** Gets the label text from the slot */ get label() { return this.textContent?.trim() || ''; } render() { return b `<slot></slot>`; } }",
277
- "description": "An option for the kr-select component."
275
+ "name": "KRSelectFieldOption",
276
+ "default": "class KRSelectFieldOption extends i$2 { constructor() { super(...arguments); /** * The option value */ this.value = ''; /** * Whether the option is disabled */ this.disabled = false; } /** Gets the label text from the slot */ get label() { return this.textContent?.trim() || ''; } render() { return b `<slot></slot>`; } }",
277
+ "description": "An option for the kr-select-field component."
278
278
  }
279
279
  ],
280
280
  "exports": [
@@ -352,17 +352,17 @@
352
352
  },
353
353
  {
354
354
  "kind": "js",
355
- "name": "KRSelect",
355
+ "name": "KRSelectField",
356
356
  "declaration": {
357
- "name": "KRSelect",
357
+ "name": "KRSelectField",
358
358
  "module": "dist/krubble.bundled.js"
359
359
  }
360
360
  },
361
361
  {
362
362
  "kind": "js",
363
- "name": "KRSelectOption",
363
+ "name": "KRSelectFieldOption",
364
364
  "declaration": {
365
- "name": "KRSelectOption",
365
+ "name": "KRSelectFieldOption",
366
366
  "module": "dist/krubble.bundled.js"
367
367
  }
368
368
  },
@@ -462,7 +462,7 @@
462
462
  {
463
463
  "kind": "class",
464
464
  "description": "",
465
- "name": "Me",
465
+ "name": "De",
466
466
  "members": [
467
467
  {
468
468
  "kind": "method",
@@ -511,8 +511,8 @@
511
511
  },
512
512
  {
513
513
  "kind": "variable",
514
- "name": "De",
515
- "default": "class extends ne{constructor(){super(...arguments),this.contentElement=null,this.dialogRef=null,this.boundHandleKeyDown=this.handleKeyDown.bind(this)}static open(e,t){const i=document.querySelector(\"kr-dialog\");i&&i.remove();const o=new Me,s=document.createElement(\"kr-dialog\");o.setDialogElement(s),s.dialogRef=o;const r=new e;return r.dialogRef=o,t?.data&&(r.data=t.data),s.contentElement=r,document.body.appendChild(s),document.addEventListener(\"keydown\",s.boundHandleKeyDown),o}handleKeyDown(e){\"Escape\"===e.key&&this.dialogRef?.close(void 0)}handleBackdropClick(e){e.target.classList.contains(\"backdrop\")&&this.dialogRef?.close(void 0)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(\"keydown\",this.boundHandleKeyDown)}render(){return U` <div class=\"backdrop\" @click=${this.handleBackdropClick}></div> <div class=\"dialog\"> ${this.contentElement} </div> `}}"
514
+ "name": "je",
515
+ "default": "class extends ne{constructor(){super(...arguments),this.contentElement=null,this.dialogRef=null,this.boundHandleKeyDown=this.handleKeyDown.bind(this)}static open(e,t){const i=document.querySelector(\"kr-dialog\");i&&i.remove();const o=new De,s=document.createElement(\"kr-dialog\");o.setDialogElement(s),s.dialogRef=o;const r=new e;return r.dialogRef=o,t?.data&&(r.data=t.data),s.contentElement=r,document.body.appendChild(s),document.addEventListener(\"keydown\",s.boundHandleKeyDown),o}handleKeyDown(e){\"Escape\"===e.key&&this.dialogRef?.close(void 0)}handleBackdropClick(e){e.target.classList.contains(\"backdrop\")&&this.dialogRef?.close(void 0)}disconnectedCallback(){super.disconnectedCallback(),document.removeEventListener(\"keydown\",this.boundHandleKeyDown)}render(){return U` <div class=\"backdrop\" @click=${this.handleBackdropClick}></div> <div class=\"dialog\"> ${this.contentElement} </div> `}}"
516
516
  },
517
517
  {
518
518
  "kind": "variable",
@@ -531,7 +531,7 @@
531
531
  {
532
532
  "kind": "variable",
533
533
  "name": "Ye",
534
- "default": "class extends ne{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._displayedColumns=[],this._widthsLocked=!1,this._resizing=null,this._resizeObserver=null,this._searchPositionLocked=!1,this._def={columns:[]},this.def={columns:[]},this._handleClickOutsideColumnPicker=e=>{if(!this._columnPickerOpen)return;const t=e.composedPath(),i=this.shadowRoot?.querySelector(\".column-picker-wrapper\");i&&!t.includes(i)&&(this._columnPickerOpen=!1)},this._handleResizeMove=e=>{if(!this._resizing)return;const t=this._def.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._handleClickOutsideColumnPicker),this._resizeObserver=new ResizeObserver(()=>{this._searchPositionLocked=!1,this._updateSearchPosition()}),this._resizeObserver.observe(this)}disconnectedCallback(){super.disconnectedCallback(),clearInterval(this._refreshTimer),document.removeEventListener(\"click\",this._handleClickOutsideColumnPicker),this._resizeObserver?.disconnect()}willUpdate(e){e.has(\"def\")&&(this._def={...this.def,columns:this.def.columns.map(e=>\"actions\"===e.type?{...e,sticky:\"right\",resizable:!1}:{...e})},this._displayedColumns=this._def.displayedColumns||this._def.columns.map(e=>e.id),this._widthsLocked=!1,this.classList.remove(\"kr-table--widths-locked\"),this._fetch(),this._initRefresh())}updated(e){this._updateScrollFlags()}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())}_fetch(){if(!this._def.dataSource)return;let e;switch(this._dataState=\"loading\",this._def.dataSource.mode){case\"opensearch\":throw Error(\"Opensearch not supported yet\");case\"db\":throw Error(\"DB not supported yet\");default:e={page:this._page-1,size:this._pageSize,sorts:[],filterFields:[],queryFields:[],facetFields:[]},this._searchQuery?.trim().length&&e.queryFields.push({name:\"_text_\",operation:\"IS\",value:Qe(this._searchQuery)})}this._def.dataSource.fetch(e).then(e=>{switch(this._def.dataSource?.mode){case\"opensearch\":throw Error(\"Opensearch not supported yet\");case\"db\":throw Error(\"DB not supported yet\");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._dataState=\"success\",this._updateSearchPosition(),this._widthsLocked||this._lockColumnWidths()}).catch(e=>{this._dataState=\"error\",Ie.show({message:e instanceof Error?e.message:\"Failed to load data\",type:\"error\"})})}_initRefresh(){clearInterval(this._refreshTimer),this._def.refreshInterval&&this._def.refreshInterval>0&&(this._refreshTimer=window.setInterval(()=>{this._fetch()},this._def.refreshInterval))}_handleSearch(e){const t=e.target;this._searchQuery=t.value,this._page=1,this._fetch()}_measureTextWidth(e,t){const i=document.createElement(\"canvas\").getContext(\"2d\");return i.font=t,i.measureText(e).width}_lockColumnWidths(){this.updateComplete.then(()=>{requestAnimationFrame(()=>{const e=this.shadowRoot?.querySelector(\".header-cell\"),t=this.shadowRoot?.querySelector(\".cell\");if(!e)return;const i=getComputedStyle(e),o=`${i.fontWeight} ${i.fontSize} ${i.fontFamily}`,s=parseFloat(i.paddingLeft)+parseFloat(i.paddingRight),r=t?getComputedStyle(t):i,n=`${r.fontWeight} ${r.fontSize} ${r.fontFamily}`,l=t?parseFloat(r.paddingLeft)+parseFloat(r.paddingRight):s;this.getDisplayedColumns().forEach(e=>{if(!e.width){let t;if(e.render){const i=this.shadowRoot?.querySelector(`.cell[data-column-id=\"${e.id}\"]`);t=i?i.scrollWidth:150}else{const i=e.label??e.id,r=this._measureTextWidth(i,o)+s;let a=0;for(const t of this._data){const i=t[e.id];if(null!=i){const e=this._measureTextWidth(String(i),n)+l;e>a&&(a=e)}}t=Math.ceil(Math.max(r,a))}e.width=`${Math.max(t,150)}px`}}),this._widthsLocked=!0,this.classList.add(\"kr-table--widths-locked\"),this.requestUpdate()})})}_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(),o=t.getBoundingClientRect().left-i.left;e.style.justifyContent=\"flex-start\",t.style.marginLeft=`${o}px`,this._searchPositionLocked=!0}))}_toggleColumnPicker(){this._columnPickerOpen=!this._columnPickerOpen}_toggleColumn(e){this._displayedColumns.includes(e)?this._displayedColumns=this._displayedColumns.filter(t=>t!==e):this._displayedColumns=[...this._displayedColumns,e]}getDisplayedColumns(){return this._displayedColumns.map(e=>this._def.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){this.dispatchEvent(new CustomEvent(\"action\",{detail:{action:e.id},bubbles:!0,composed:!0}))}_renderCellContent(e,t){const i=t[e.id];if(e.render){const i=e.render(t);return\"string\"==typeof i?Oe(i):i}if(null==i)return\"\";switch(e.type){case\"number\":return\"number\"==typeof i?i.toLocaleString():String(i);case\"currency\":return\"number\"==typeof i?i.toLocaleString(\"en-US\",{style:\"currency\",currency:\"USD\"}):String(i);case\"date\":return i instanceof Date?i.toLocaleDateString():new Date(i).toLocaleDateString();case\"boolean\":return!0===i?\"Yes\":!1===i?\"No\":\"\";default:return String(i)}}_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--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 U` <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._def.title&&!this._def.actions?.length&&this._totalPages<=1?V:U` <div class=\"header\"> <div class=\"title\">${this._def.title??\"\"}</div> <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._def.columns].filter(e=>\"actions\"!==e.type).sort((e,t)=>(e.label??e.id).localeCompare(t.label??t.id)).map(e=>U` <div class=\"column-picker-item\" @click=${()=>this._toggleColumn(e.id)}> <div class=\"column-picker-checkbox ${this._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._def.actions?.length?U` <kr-button class=\"actions\" @click=${()=>this._handleAction(this._def.actions[0])} > ${this._def.actions[0].label} </kr-button> `:this._def.actions?.length?U` <kr-button class=\"actions\" .options=${this._def.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> `:V} </div> </div> `}_renderStatus(){return\"loading\"===this._dataState&&0===this._data.length?U`<div class=\"status\">Loading...</div>`:\"error\"===this._dataState&&0===this._data.length?U`<div class=\"status status--error\">Error loading data</div>`:0===this._data.length?U`<div class=\"status\">No data available</div>`:V}_getGridTemplateColumns(){const e=this.getDisplayedColumns(),t=e.map((e,t)=>e.sticky?-1:t).filter(e=>e>=0).pop();return e.map((e,i)=>i===t&&this._widthsLocked?`minmax(${e.width||\"auto\"}, 1fr)`:e.width||\"auto\").join(\" \")}_renderTable(){return U` <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)=>U` <div class=${we(this._getHeaderCellClasses(e,t))} style=${Ue(this._getCellStyle(e,t))} data-column-id=${e.id} >${e.label??e.id}${!1!==e.resizable?U`<div class=\"header-cell__resize\" @mousedown=${t=>this._handleResizeStart(t,e.id)} ></div>`:V}</div> `)} </div> ${this._data.map(e=>U` <div class=\"row\"> ${this.getDisplayedColumns().map((t,i)=>U` <div class=${we(this._getCellClasses(t,i))} style=${Ue(this._getCellStyle(t,i))} data-column-id=${t.id} > ${this._renderCellContent(t,e)} </div> `)} </div> `)} </div> </div> </div> `}render(){return this._def.columns.length?U` ${this._renderHeader()} ${this._renderTable()} `:U`<slot></slot>`}}"
534
+ "default": "class extends ne{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._displayedColumns=[],this._resizing=null,this._resizeObserver=null,this._searchPositionLocked=!1,this._def={columns:[]},this.def={columns:[]},this._handleClickOutsideColumnPicker=e=>{if(!this._columnPickerOpen)return;const t=e.composedPath(),i=this.shadowRoot?.querySelector(\".column-picker-wrapper\");i&&!t.includes(i)&&(this._columnPickerOpen=!1)},this._handleResizeMove=e=>{if(!this._resizing)return;const t=this._def.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._handleClickOutsideColumnPicker),this._resizeObserver=new ResizeObserver(()=>{this._searchPositionLocked=!1,this._updateSearchPosition()}),this._resizeObserver.observe(this)}disconnectedCallback(){super.disconnectedCallback(),clearInterval(this._refreshTimer),document.removeEventListener(\"click\",this._handleClickOutsideColumnPicker),this._resizeObserver?.disconnect()}willUpdate(e){e.has(\"def\")&&(this._def={...this.def,columns:this.def.columns.map(e=>\"actions\"===e.type?{...e,sticky:\"right\",resizable:!1}:{...e})},this._displayedColumns=this._def.displayedColumns||this._def.columns.map(e=>e.id),this._fetch(),this._initRefresh())}updated(e){this._updateScrollFlags()}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())}_fetch(){if(!this._def.dataSource)return;let e;switch(this._dataState=\"loading\",this._def.dataSource.mode){case\"opensearch\":throw Error(\"Opensearch not supported yet\");case\"db\":throw Error(\"DB not supported yet\");default:e={page:this._page-1,size:this._pageSize,sorts:[],filterFields:[],queryFields:[],facetFields:[]},this._searchQuery?.trim().length&&e.queryFields.push({name:\"_text_\",operation:\"IS\",value:Qe(this._searchQuery)})}this._def.dataSource.fetch(e).then(e=>{switch(this._def.dataSource?.mode){case\"opensearch\":throw Error(\"Opensearch not supported yet\");case\"db\":throw Error(\"DB not supported yet\");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._dataState=\"success\",this._updateSearchPosition()}).catch(e=>{this._dataState=\"error\",Ie.show({message:e instanceof Error?e.message:\"Failed to load data\",type:\"error\"})})}_initRefresh(){clearInterval(this._refreshTimer),this._def.refreshInterval&&this._def.refreshInterval>0&&(this._refreshTimer=window.setInterval(()=>{this._fetch()},this._def.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(),o=t.getBoundingClientRect().left-i.left;e.style.justifyContent=\"flex-start\",t.style.marginLeft=`${o}px`,this._searchPositionLocked=!0}))}_toggleColumnPicker(){this._columnPickerOpen=!this._columnPickerOpen}_toggleColumn(e){this._displayedColumns.includes(e)?this._displayedColumns=this._displayedColumns.filter(t=>t!==e):this._displayedColumns=[...this._displayedColumns,e]}getDisplayedColumns(){return this._displayedColumns.map(e=>this._def.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){this.dispatchEvent(new CustomEvent(\"action\",{detail:{action:e.id},bubbles:!0,composed:!0}))}_renderCellContent(e,t){const i=t[e.id];if(e.render){const i=e.render(t);return\"string\"==typeof i?Oe(i):i}if(null==i)return\"\";switch(e.type){case\"number\":return\"number\"==typeof i?i.toLocaleString():String(i);case\"currency\":return\"number\"==typeof i?i.toLocaleString(\"en-US\",{style:\"currency\",currency:\"USD\"}):String(i);case\"date\":return i instanceof Date?i.toLocaleDateString():new Date(i).toLocaleDateString();case\"boolean\":return!0===i?\"Yes\":!1===i?\"No\":\"\";default:return String(i)}}_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 U` <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._def.title&&!this._def.actions?.length&&this._totalPages<=1?V:U` <div class=\"header\"> <div class=\"title\">${this._def.title??\"\"}</div> <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._def.columns].filter(e=>\"actions\"!==e.type).sort((e,t)=>(e.label??e.id).localeCompare(t.label??t.id)).map(e=>U` <div class=\"column-picker-item\" @click=${()=>this._toggleColumn(e.id)}> <div class=\"column-picker-checkbox ${this._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._def.actions?.length?U` <kr-button class=\"actions\" @click=${()=>this._handleAction(this._def.actions[0])} > ${this._def.actions[0].label} </kr-button> `:this._def.actions?.length?U` <kr-button class=\"actions\" .options=${this._def.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> `:V} </div> </div> `}_renderStatus(){return\"loading\"===this._dataState&&0===this._data.length?U`<div class=\"status\">Loading...</div>`:\"error\"===this._dataState&&0===this._data.length?U`<div class=\"status status--error\">Error loading data</div>`:0===this._data.length?U`<div class=\"status\">No data available</div>`:V}_renderTable(){return U` <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)=>U` <div class=${we(this._getHeaderCellClasses(e,t))} style=${Ue(this._getCellStyle(e,t))} data-column-id=${e.id} >${e.label??e.id}${!1!==e.resizable?U`<div class=\"header-cell__resize\" @mousedown=${t=>this._handleResizeStart(t,e.id)} ></div>`:V}</div> `)} </div> ${this._data.map(e=>U` <div class=\"row\"> ${this.getDisplayedColumns().map((t,i)=>U` <div class=${we(this._getCellClasses(t,i))} style=${Ue(this._getCellStyle(t,i))} data-column-id=${t.id} > ${this._renderCellContent(t,e)} </div> `)} </div> `)} </div> </div> </div> `}render(){return this._def.columns.length?U` ${this._renderHeader()} ${this._renderTable()} `:U`<slot></slot>`}}"
535
535
  },
536
536
  {
537
537
  "kind": "variable",
@@ -541,7 +541,7 @@
541
541
  {
542
542
  "kind": "variable",
543
543
  "name": "st",
544
- "default": "class extends ne{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=>{this.contains(e.target)||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)}}_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 U` <div class=\"wrapper\"> ${this.label?U` <label> ${this.label} ${this.required?U`<span class=\"required\" aria-hidden=\"true\">*</span>`:\"\"} </label> `:V} <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 20 20\" fill=\"currentColor\" > <path fill-rule=\"evenodd\" d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\" clip-rule=\"evenodd\" /> </svg> </button> <div class=${we({\"select-dropdown\":!0,hidden:!this._isOpen})} role=\"listbox\"> <div class=\"select-options\"> ${0===e.length?U`<div class=\"select-empty\">No options available</div>`:e.map((e,t)=>{const i=e.value===this.value;return U` <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} ${i?U`<svg class=\"chevron-icon select-check\" viewBox=\"0 0 20 20\" fill=\"currentColor\"> <path fill-rule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clip-rule=\"evenodd\" /> </svg>`:V} </div> `})} </div> </div> </div> ${this._touched&&this.required&&!this.value?U`<div class=\"validation-message\">Please select an option</div>`:this.hint?U`<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()}}"
544
+ "default": "class extends ne{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-field-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-field-option\"));this._highlightedIndex=e.findIndex(e=>e.value===this.value)}}_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-field-option\")),t=e.find(e=>e.value===this.value)?.label;return U` <div class=\"wrapper\"> ${this.label?U` <label> ${this.label} ${this.required?U`<span class=\"required\" aria-hidden=\"true\">*</span>`:\"\"} </label> `:V} <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 20 20\" fill=\"currentColor\" > <path fill-rule=\"evenodd\" d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\" clip-rule=\"evenodd\" /> </svg> </button> <div class=${we({\"select-dropdown\":!0,hidden:!this._isOpen})} role=\"listbox\"> <div class=\"select-options\"> ${0===e.length?U`<div class=\"select-empty\">No options available</div>`:e.map((e,t)=>{const i=e.value===this.value;return U` <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} ${i?U`<svg class=\"chevron-icon select-check\" viewBox=\"0 0 20 20\" fill=\"currentColor\"> <path fill-rule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clip-rule=\"evenodd\" /> </svg>`:V} </div> `})} </div> </div> </div> ${this._touched&&this.required&&!this.value?U`<div class=\"validation-message\">Please select an option</div>`:this.hint?U`<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()}}"
545
545
  },
546
546
  {
547
547
  "kind": "variable",
@@ -570,7 +570,7 @@
570
570
  "kind": "js",
571
571
  "name": "DialogRef",
572
572
  "declaration": {
573
- "name": "Me",
573
+ "name": "De",
574
574
  "module": "dist/krubble.bundled.min.js"
575
575
  }
576
576
  },
@@ -618,13 +618,13 @@
618
618
  "kind": "js",
619
619
  "name": "KRDialog",
620
620
  "declaration": {
621
- "name": "De",
621
+ "name": "je",
622
622
  "module": "dist/krubble.bundled.min.js"
623
623
  }
624
624
  },
625
625
  {
626
626
  "kind": "js",
627
- "name": "KRSelect",
627
+ "name": "KRSelectField",
628
628
  "declaration": {
629
629
  "name": "st",
630
630
  "module": "dist/krubble.bundled.min.js"
@@ -632,7 +632,7 @@
632
632
  },
633
633
  {
634
634
  "kind": "js",
635
- "name": "KRSelectOption",
635
+ "name": "KRSelectFieldOption",
636
636
  "declaration": {
637
637
  "name": "nt",
638
638
  "module": "dist/krubble.bundled.min.js"
@@ -916,17 +916,17 @@
916
916
  },
917
917
  {
918
918
  "kind": "js",
919
- "name": "KRSelect",
919
+ "name": "KRSelectField",
920
920
  "declaration": {
921
- "name": "KRSelect",
921
+ "name": "KRSelectField",
922
922
  "module": "./form/index.js"
923
923
  }
924
924
  },
925
925
  {
926
926
  "kind": "js",
927
- "name": "KRSelectOption",
927
+ "name": "KRSelectFieldOption",
928
928
  "declaration": {
929
- "name": "KRSelectOption",
929
+ "name": "KRSelectFieldOption",
930
930
  "module": "./form/index.js"
931
931
  }
932
932
  },
@@ -988,44 +988,44 @@
988
988
  },
989
989
  {
990
990
  "kind": "javascript-module",
991
- "path": "dist/button/button.js",
991
+ "path": "dist/alert/alert.js",
992
992
  "declarations": [
993
993
  {
994
994
  "kind": "variable",
995
- "name": "KRButton",
996
- "default": "class KRButton extends LitElement { constructor() { super(...arguments); /** * The button variant (shape) */ this.variant = 'flat'; /** * The button color */ this.color = 'primary'; /** * The button size */ this.size = 'medium'; /** * Whether the button is disabled */ this.disabled = false; /** * Dropdown options - when provided, button becomes a dropdown */ this.options = []; this._state = 'idle'; this._stateText = ''; this._dropdownOpened = false; this._dropdownAlignRight = false; this._handleHostClick = (e) => { if (this.options.length) { e.stopPropagation(); this._toggleDropdown(); } }; this._handleKeydown = (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if (this.options.length) { this._toggleDropdown(); } else { this.click(); } } if (e.key === 'Escape' && this._dropdownOpened) { this._dropdownOpened = false; } }; this._handleClickOutside = (e) => { if (this._dropdownOpened && !this.contains(e.target)) { this._dropdownOpened = false; } }; } connectedCallback() { super.connectedCallback(); this.setAttribute('role', '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; if (this._dropdownOpened) { // Check if dropdown would overflow viewport after render requestAnimationFrame(() => { const dropdown = this.shadowRoot?.querySelector('.dropdown'); if (dropdown) { const rect = dropdown.getBoundingClientRect(); this._dropdownAlignRight = rect.right > window.innerWidth; } }); } } _handleOptionClick(option, e) { e.stopPropagation(); this._dropdownOpened = false; this.dispatchEvent(new CustomEvent('option-select', { detail: { id: option.id, label: option.label }, bubbles: true, composed: true })); } /** * Shows a loading spinner and disables the button. */ showLoading() { this._clearStateTimeout(); this._state = 'loading'; this._stateText = ''; } /** * Shows a success state with optional custom text. * @param text - Text to display (default: \"Saved\") * @param duration - Duration in ms before auto-reset (default: 2000) */ showSuccess(text = 'Success', duration = 2000) { this._clearStateTimeout(); this._state = 'success'; this._stateText = text; this._stateTimeout = window.setTimeout(() => this.reset(), duration); } /** * Shows an error state with optional custom text. * @param text - Text to display (default: \"Error\") * @param duration - Duration in ms before auto-reset (default: 2000) */ showError(text = 'Error', duration = 2000) { this._clearStateTimeout(); this._state = 'error'; this._stateText = text; this._stateTimeout = window.setTimeout(() => this.reset(), duration); } /** * Resets the button to its idle state. */ reset() { this._clearStateTimeout(); this._state = 'idle'; this._stateText = ''; } _clearStateTimeout() { if (this._stateTimeout) { clearTimeout(this._stateTimeout); this._stateTimeout = undefined; } } updated(changedProperties) { // Reflect state classes to host this.classList.toggle('kr-button--loading', this._state === 'loading'); this.classList.toggle('kr-button--success', this._state === 'success'); this.classList.toggle('kr-button--error', this._state === 'error'); this.classList.toggle(`kr-button--${this.variant}`, true); this.classList.toggle(`kr-button--${this.color}`, true); this.classList.toggle('kr-button--small', this.size === 'small'); this.classList.toggle('kr-button--large', this.size === 'large'); } render() { return html ` <slot></slot> ${this.options.length ? html `<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>` : nothing} ${this._state !== 'idle' ? html `<span class=\"state-overlay\"> ${this._state === 'loading' ? html `<span class=\"spinner\"></span>` : this._stateText} </span>` : nothing} ${this.options.length ? html ` <div class=\"dropdown ${this._dropdownOpened ? 'dropdown--opened' : ''} ${this._dropdownAlignRight ? 'dropdown--align-right' : ''}\"> ${this.options.map(option => html ` <button class=\"dropdown-item\" @click=${(e) => this._handleOptionClick(option, e)} >${option.label}</button> `)} </div> ` : nothing} `; } }",
997
- "description": "A customizable button component."
995
+ "name": "KRAlert",
996
+ "default": "class KRAlert extends LitElement { constructor() { super(...arguments); /** * The alert type/severity */ this.type = 'info'; /** * Whether the alert can be dismissed */ this.dismissible = false; /** * Whether the alert is visible */ this.visible = true; } /** Handles dismiss button click */ _handleDismiss() { this.visible = false; this.dispatchEvent(new CustomEvent('dismiss', { bubbles: true, composed: true })); } render() { const icons = { info: html `<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: html `<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: html `<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: html `<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 html ` <div class=${classMap({ 'alert': true, ['alert--' + this.type]: true, 'alert--hidden': !this.visible })} role=\"alert\" > ${icons[this.type]} <div class=\"content\"> ${this.header ? html `<h4 class=\"header\">${this.header}</h4>` : nothing} <div class=\"message\"> <slot></slot> </div> </div> ${this.dismissible ? html ` <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> ` : nothing} </div> `; } }",
997
+ "description": "A customizable alert component for displaying important information to users."
998
998
  }
999
999
  ],
1000
1000
  "exports": [
1001
1001
  {
1002
1002
  "kind": "js",
1003
- "name": "KRButton",
1003
+ "name": "KRAlert",
1004
1004
  "declaration": {
1005
- "name": "KRButton",
1006
- "module": "dist/button/button.js"
1005
+ "name": "KRAlert",
1006
+ "module": "dist/alert/alert.js"
1007
1007
  }
1008
1008
  }
1009
1009
  ]
1010
1010
  },
1011
1011
  {
1012
1012
  "kind": "javascript-module",
1013
- "path": "dist/alert/alert.js",
1013
+ "path": "dist/button/button.js",
1014
1014
  "declarations": [
1015
1015
  {
1016
1016
  "kind": "variable",
1017
- "name": "KRAlert",
1018
- "default": "class KRAlert extends LitElement { constructor() { super(...arguments); /** * The alert type/severity */ this.type = 'info'; /** * Whether the alert can be dismissed */ this.dismissible = false; /** * Whether the alert is visible */ this.visible = true; } /** Handles dismiss button click */ _handleDismiss() { this.visible = false; this.dispatchEvent(new CustomEvent('dismiss', { bubbles: true, composed: true })); } render() { const icons = { info: html `<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: html `<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: html `<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: html `<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 html ` <div class=${classMap({ 'alert': true, ['alert--' + this.type]: true, 'alert--hidden': !this.visible })} role=\"alert\" > ${icons[this.type]} <div class=\"content\"> ${this.header ? html `<h4 class=\"header\">${this.header}</h4>` : nothing} <div class=\"message\"> <slot></slot> </div> </div> ${this.dismissible ? html ` <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> ` : nothing} </div> `; } }",
1019
- "description": "A customizable alert component for displaying important information to users."
1017
+ "name": "KRButton",
1018
+ "default": "class KRButton extends LitElement { constructor() { super(...arguments); /** * The button variant (shape) */ this.variant = 'flat'; /** * The button color */ this.color = 'primary'; /** * The button size */ this.size = 'medium'; /** * Whether the button is disabled */ this.disabled = false; /** * Dropdown options - when provided, button becomes a dropdown */ this.options = []; this._state = 'idle'; this._stateText = ''; this._dropdownOpened = false; this._dropdownAlignRight = false; this._handleHostClick = (e) => { if (this.options.length) { e.stopPropagation(); this._toggleDropdown(); } }; this._handleKeydown = (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if (this.options.length) { this._toggleDropdown(); } else { this.click(); } } if (e.key === 'Escape' && this._dropdownOpened) { this._dropdownOpened = false; } }; this._handleClickOutside = (e) => { if (this._dropdownOpened && !this.contains(e.target)) { this._dropdownOpened = false; } }; } connectedCallback() { super.connectedCallback(); this.setAttribute('role', '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; if (this._dropdownOpened) { // Check if dropdown would overflow viewport after render requestAnimationFrame(() => { const dropdown = this.shadowRoot?.querySelector('.dropdown'); if (dropdown) { const rect = dropdown.getBoundingClientRect(); this._dropdownAlignRight = rect.right > window.innerWidth; } }); } } _handleOptionClick(option, e) { e.stopPropagation(); this._dropdownOpened = false; this.dispatchEvent(new CustomEvent('option-select', { detail: { id: option.id, label: option.label }, bubbles: true, composed: true })); } /** * Shows a loading spinner and disables the button. */ showLoading() { this._clearStateTimeout(); this._state = 'loading'; this._stateText = ''; } /** * Shows a success state with optional custom text. * @param text - Text to display (default: \"Saved\") * @param duration - Duration in ms before auto-reset (default: 2000) */ showSuccess(text = 'Success', duration = 2000) { this._clearStateTimeout(); this._state = 'success'; this._stateText = text; this._stateTimeout = window.setTimeout(() => this.reset(), duration); } /** * Shows an error state with optional custom text. * @param text - Text to display (default: \"Error\") * @param duration - Duration in ms before auto-reset (default: 2000) */ showError(text = 'Error', duration = 2000) { this._clearStateTimeout(); this._state = 'error'; this._stateText = text; this._stateTimeout = window.setTimeout(() => this.reset(), duration); } /** * Resets the button to its idle state. */ reset() { this._clearStateTimeout(); this._state = 'idle'; this._stateText = ''; } _clearStateTimeout() { if (this._stateTimeout) { clearTimeout(this._stateTimeout); this._stateTimeout = undefined; } } updated(changedProperties) { // Reflect state classes to host this.classList.toggle('kr-button--loading', this._state === 'loading'); this.classList.toggle('kr-button--success', this._state === 'success'); this.classList.toggle('kr-button--error', this._state === 'error'); this.classList.toggle(`kr-button--${this.variant}`, true); this.classList.toggle(`kr-button--${this.color}`, true); this.classList.toggle('kr-button--small', this.size === 'small'); this.classList.toggle('kr-button--large', this.size === 'large'); } render() { return html ` <slot></slot> ${this.options.length ? html `<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>` : nothing} ${this._state !== 'idle' ? html `<span class=\"state-overlay\"> ${this._state === 'loading' ? html `<span class=\"spinner\"></span>` : this._stateText} </span>` : nothing} ${this.options.length ? html ` <div class=\"dropdown ${this._dropdownOpened ? 'dropdown--opened' : ''} ${this._dropdownAlignRight ? 'dropdown--align-right' : ''}\"> ${this.options.map(option => html ` <button class=\"dropdown-item\" @click=${(e) => this._handleOptionClick(option, e)} >${option.label}</button> `)} </div> ` : nothing} `; } }",
1019
+ "description": "A customizable button component."
1020
1020
  }
1021
1021
  ],
1022
1022
  "exports": [
1023
1023
  {
1024
1024
  "kind": "js",
1025
- "name": "KRAlert",
1025
+ "name": "KRButton",
1026
1026
  "declaration": {
1027
- "name": "KRAlert",
1028
- "module": "dist/alert/alert.js"
1027
+ "name": "KRButton",
1028
+ "module": "dist/button/button.js"
1029
1029
  }
1030
1030
  }
1031
1031
  ]
@@ -1052,6 +1052,28 @@
1052
1052
  }
1053
1053
  ]
1054
1054
  },
1055
+ {
1056
+ "kind": "javascript-module",
1057
+ "path": "dist/context-menu/context-menu.js",
1058
+ "declarations": [
1059
+ {
1060
+ "kind": "variable",
1061
+ "name": "KRContextMenu",
1062
+ "default": "class KRContextMenu extends LitElement { constructor() { super(...arguments); this.items = []; this.resolvePromise = null; this.boundHandleOutsideClick = this.handleOutsideClick.bind(this); this.boundHandleKeyDown = this.handleKeyDown.bind(this); } static async open(options) { // Remove any existing context menu const existing = document.querySelector('kr-context-menu'); if (existing) { existing.remove(); } // Create and position the menu const menu = document.createElement('kr-context-menu'); document.body.appendChild(menu); return menu.show(options); } async show(options) { this.items = options.items; this.style.left = `${options.x}px`; this.style.top = `${options.y}px`; // Adjust position if menu would go off screen await this.updateComplete; const rect = this.getBoundingClientRect(); if (rect.right > window.innerWidth) { this.style.left = `${options.x - rect.width}px`; } if (rect.bottom > window.innerHeight) { this.style.top = `${options.y - rect.height}px`; } // Add event listeners after a microtask to avoid the current event triggering close requestAnimationFrame(() => { document.addEventListener('click', this.boundHandleOutsideClick); document.addEventListener('contextmenu', this.boundHandleOutsideClick); document.addEventListener('keydown', this.boundHandleKeyDown); }); return new Promise((resolve) => { this.resolvePromise = resolve; }); } handleOutsideClick(e) { if (!this.contains(e.target)) { this.close(null); } } handleKeyDown(e) { if (e.key === 'Escape') { this.close(null); } } handleItemClick(item) { if (!item.disabled && !item.divider) { this.close(item); } } close(result) { document.removeEventListener('click', this.boundHandleOutsideClick); document.removeEventListener('contextmenu', this.boundHandleOutsideClick); document.removeEventListener('keydown', this.boundHandleKeyDown); if (this.resolvePromise) { this.resolvePromise(result); this.resolvePromise = null; } this.remove(); } render() { return html ` <div class=\"menu\"> ${this.items.map(item => item.divider ? html `<div class=\"menu__divider\"></div>` : html ` <button class=\"menu__item\" ?disabled=${item.disabled} @click=${() => this.handleItemClick(item)} > ${item.icon ? html `<span class=\"menu__item-icon\">${item.icon}</span>` : null} ${item.label} </button> `)} </div> `; } }",
1063
+ "description": "Context menu component that can be opened programmatically.\n\nUsage:\n```ts\nconst result = await ContextMenu.open({\n x: event.clientX,\n y: event.clientY,\n items: [\n { id: 'edit', label: 'Edit Item' },\n { id: 'divider', label: '', divider: true },\n { id: 'add-above', label: 'Add Item Above' },\n { id: 'add-below', label: 'Add Item Below' },\n ]\n});\n\nif (result) {\n console.log('Selected:', result.id);\n}\n```"
1064
+ }
1065
+ ],
1066
+ "exports": [
1067
+ {
1068
+ "kind": "js",
1069
+ "name": "KRContextMenu",
1070
+ "declaration": {
1071
+ "name": "KRContextMenu",
1072
+ "module": "dist/context-menu/context-menu.js"
1073
+ }
1074
+ }
1075
+ ]
1076
+ },
1055
1077
  {
1056
1078
  "kind": "javascript-module",
1057
1079
  "path": "dist/dialog/dialog.js",
@@ -1123,28 +1145,6 @@
1123
1145
  }
1124
1146
  ]
1125
1147
  },
1126
- {
1127
- "kind": "javascript-module",
1128
- "path": "dist/context-menu/context-menu.js",
1129
- "declarations": [
1130
- {
1131
- "kind": "variable",
1132
- "name": "KRContextMenu",
1133
- "default": "class KRContextMenu extends LitElement { constructor() { super(...arguments); this.items = []; this.resolvePromise = null; this.boundHandleOutsideClick = this.handleOutsideClick.bind(this); this.boundHandleKeyDown = this.handleKeyDown.bind(this); } static async open(options) { // Remove any existing context menu const existing = document.querySelector('kr-context-menu'); if (existing) { existing.remove(); } // Create and position the menu const menu = document.createElement('kr-context-menu'); document.body.appendChild(menu); return menu.show(options); } async show(options) { this.items = options.items; this.style.left = `${options.x}px`; this.style.top = `${options.y}px`; // Adjust position if menu would go off screen await this.updateComplete; const rect = this.getBoundingClientRect(); if (rect.right > window.innerWidth) { this.style.left = `${options.x - rect.width}px`; } if (rect.bottom > window.innerHeight) { this.style.top = `${options.y - rect.height}px`; } // Add event listeners after a microtask to avoid the current event triggering close requestAnimationFrame(() => { document.addEventListener('click', this.boundHandleOutsideClick); document.addEventListener('contextmenu', this.boundHandleOutsideClick); document.addEventListener('keydown', this.boundHandleKeyDown); }); return new Promise((resolve) => { this.resolvePromise = resolve; }); } handleOutsideClick(e) { if (!this.contains(e.target)) { this.close(null); } } handleKeyDown(e) { if (e.key === 'Escape') { this.close(null); } } handleItemClick(item) { if (!item.disabled && !item.divider) { this.close(item); } } close(result) { document.removeEventListener('click', this.boundHandleOutsideClick); document.removeEventListener('contextmenu', this.boundHandleOutsideClick); document.removeEventListener('keydown', this.boundHandleKeyDown); if (this.resolvePromise) { this.resolvePromise(result); this.resolvePromise = null; } this.remove(); } render() { return html ` <div class=\"menu\"> ${this.items.map(item => item.divider ? html `<div class=\"menu__divider\"></div>` : html ` <button class=\"menu__item\" ?disabled=${item.disabled} @click=${() => this.handleItemClick(item)} > ${item.icon ? html `<span class=\"menu__item-icon\">${item.icon}</span>` : null} ${item.label} </button> `)} </div> `; } }",
1134
- "description": "Context menu component that can be opened programmatically.\n\nUsage:\n```ts\nconst result = await ContextMenu.open({\n x: event.clientX,\n y: event.clientY,\n items: [\n { id: 'edit', label: 'Edit Item' },\n { id: 'divider', label: '', divider: true },\n { id: 'add-above', label: 'Add Item Above' },\n { id: 'add-below', label: 'Add Item Below' },\n ]\n});\n\nif (result) {\n console.log('Selected:', result.id);\n}\n```"
1135
- }
1136
- ],
1137
- "exports": [
1138
- {
1139
- "kind": "js",
1140
- "name": "KRContextMenu",
1141
- "declaration": {
1142
- "name": "KRContextMenu",
1143
- "module": "dist/context-menu/context-menu.js"
1144
- }
1145
- }
1146
- ]
1147
- },
1148
1148
  {
1149
1149
  "kind": "javascript-module",
1150
1150
  "path": "dist/form/index.js",
@@ -1160,18 +1160,18 @@
1160
1160
  },
1161
1161
  {
1162
1162
  "kind": "js",
1163
- "name": "KRSelect",
1163
+ "name": "KRSelectField",
1164
1164
  "declaration": {
1165
- "name": "KRSelect",
1166
- "module": "./select/select.js"
1165
+ "name": "KRSelectField",
1166
+ "module": "./select-field/select-field.js"
1167
1167
  }
1168
1168
  },
1169
1169
  {
1170
1170
  "kind": "js",
1171
- "name": "KRSelectOption",
1171
+ "name": "KRSelectFieldOption",
1172
1172
  "declaration": {
1173
- "name": "KRSelectOption",
1174
- "module": "./select/select-option.js"
1173
+ "name": "KRSelectFieldOption",
1174
+ "module": "./select-field/select-field-option.js"
1175
1175
  }
1176
1176
  }
1177
1177
  ]
@@ -1226,7 +1226,7 @@
1226
1226
  {
1227
1227
  "kind": "variable",
1228
1228
  "name": "KRTable",
1229
- "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._displayedColumns = []; this._widthsLocked = false; this._resizing = null; this._resizeObserver = null; this._searchPositionLocked = false; this._def = { columns: [] }; this.def = { columns: [] }; this._handleClickOutsideColumnPicker = (e) => { if (!this._columnPickerOpen) return; const path = e.composedPath(); const picker = this.shadowRoot?.querySelector('.column-picker-wrapper'); if (picker && !path.includes(picker)) { this._columnPickerOpen = false; } }; this._handleResizeMove = (e) => { if (!this._resizing) return; const col = this._def.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._handleClickOutsideColumnPicker); 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._handleClickOutsideColumnPicker); this._resizeObserver?.disconnect(); } willUpdate(changedProperties) { if (changedProperties.has('def')) { // Copy user's def and normalize action columns this._def = { ...this.def, columns: this.def.columns.map(col => { if (col.type === 'actions') { return { ...col, sticky: 'right', resizable: false }; } return { ...col }; }) }; this._displayedColumns = this._def.displayedColumns || this._def.columns.map(c => c.id); this._widthsLocked = false; this.classList.remove('kr-table--widths-locked'); this._fetch(); this._initRefresh(); } } updated(changedProperties) { this._updateScrollFlags(); } // ---------------------------------------------------------------------------- // 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 // ---------------------------------------------------------------------------- /** * 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._def.dataSource) return; this._dataState = 'loading'; // Build request based on mode let request; switch (this._def.dataSource.mode) { case 'opensearch': throw Error('Opensearch not supported yet'); case 'db': throw Error('DB not supported yet'); default: // solr request = { page: this._page - 1, size: this._pageSize, sorts: [], filterFields: [], queryFields: [], facetFields: [] }; if (this._searchQuery?.trim().length) { request.queryFields.push({ name: '_text_', operation: 'IS', value: escapeSolrQuery(this._searchQuery) }); } } this._def.dataSource.fetch(request) .then(response => { // Parse response based on mode switch (this._def.dataSource?.mode) { case 'opensearch': { throw Error('Opensearch not supported yet'); break; } case 'db': { throw Error('DB not supported yet'); 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._dataState = 'success'; this._updateSearchPosition(); if (!this._widthsLocked) this._lockColumnWidths(); }) .catch(err => { this._dataState = 'error'; KRSnackbar.show({ message: err instanceof Error ? err.message : 'Failed to load data', type: 'error' }); }); } /** * 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._def.refreshInterval && this._def.refreshInterval > 0) { this._refreshTimer = window.setInterval(() => { this._fetch(); }, this._def.refreshInterval); } } _handleSearch(e) { const input = e.target; this._searchQuery = input.value; this._page = 1; this._fetch(); } _measureTextWidth(text, font) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.font = font; return ctx.measureText(text).width; } _lockColumnWidths() { this.updateComplete.then(() => { requestAnimationFrame(() => { const headerCell = this.shadowRoot?.querySelector('.header-cell'); const dataCell = this.shadowRoot?.querySelector('.cell'); if (!headerCell) return; const headerStyle = getComputedStyle(headerCell); const headerFont = `${headerStyle.fontWeight} ${headerStyle.fontSize} ${headerStyle.fontFamily}`; const headerPadding = parseFloat(headerStyle.paddingLeft) + parseFloat(headerStyle.paddingRight); const dataStyle = dataCell ? getComputedStyle(dataCell) : headerStyle; const dataFont = `${dataStyle.fontWeight} ${dataStyle.fontSize} ${dataStyle.fontFamily}`; const dataPadding = dataCell ? parseFloat(dataStyle.paddingLeft) + parseFloat(dataStyle.paddingRight) : headerPadding; this.getDisplayedColumns().forEach(col => { if (!col.width) { let width; // For columns with custom render functions, measure the actual DOM element if (col.render) { const cell = this.shadowRoot?.querySelector(`.cell[data-column-id=\"${col.id}\"]`); width = cell ? cell.scrollWidth : 150; } else { const headerText = col.label ?? col.id; const headerWidth = this._measureTextWidth(headerText, headerFont) + headerPadding; let maxDataWidth = 0; for (const row of this._data) { const value = row[col.id]; if (value != null) { const dataWidth = this._measureTextWidth(String(value), dataFont) + dataPadding; if (dataWidth > maxDataWidth) maxDataWidth = dataWidth; } } width = Math.ceil(Math.max(headerWidth, maxDataWidth)); } col.width = `${Math.max(width, 150)}px`; } }); this._widthsLocked = true; this.classList.add('kr-table--widths-locked'); this.requestUpdate(); }); }); } /** * 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._displayedColumns.includes(columnId)) { this._displayedColumns = this._displayedColumns.filter(id => id !== columnId); } else { this._displayedColumns = [...this._displayedColumns, columnId]; } } // 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._displayedColumns .map(id => this._def.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) { this.dispatchEvent(new CustomEvent('action', { detail: { action: action.id }, bubbles: true, composed: true })); } // ---------------------------------------------------------------------------- // Rendering // ---------------------------------------------------------------------------- _renderCellContent(column, row) { const value = row[column.id]; if (column.render) { const result = column.render(row); // If render returns a string, treat it as HTML return typeof result === 'string' ? unsafeHTML(result) : result; } if (value === null || value === undefined) { return ''; } switch (column.type) { case 'number': return typeof value === 'number' ? value.toLocaleString() : String(value); case 'currency': return typeof value === 'number' ? value.toLocaleString('en-US', { style: 'currency', currency: 'USD' }) : String(value); case 'date': return value instanceof Date ? value.toLocaleDateString() : new Date(value).toLocaleDateString(); 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--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._def.title && !this._def.actions?.length && this._totalPages <= 1) { return nothing; } return html ` <div class=\"header\"> <div class=\"title\">${this._def.title ?? ''}</div> <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._def.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._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._def.actions?.length === 1 ? html ` <kr-button class=\"actions\" @click=${() => this._handleAction(this._def.actions[0])} > ${this._def.actions[0].label} </kr-button> ` : this._def.actions?.length ? html ` <kr-button class=\"actions\" .options=${this._def.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; } _getGridTemplateColumns() { const cols = this.getDisplayedColumns(); const lastNonStickyIndex = cols.map((c, i) => c.sticky ? -1 : i).filter(i => i >= 0).pop(); return cols.map((col, i) => { if (i === lastNonStickyIndex && this._widthsLocked) { return `minmax(${col.width || 'auto'}, 1fr)`; } return col.width || 'auto'; }).join(' '); } /** 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._data.map(row => html ` <div class=\"row\"> ${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)} </div> `)} </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._def.columns.length) { return html `<slot></slot>`; } return html ` ${this._renderHeader()} ${this._renderTable()} `; } }"
1229
+ "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._displayedColumns = []; this._resizing = null; this._resizeObserver = null; this._searchPositionLocked = false; this._def = { columns: [] }; this.def = { columns: [] }; this._handleClickOutsideColumnPicker = (e) => { if (!this._columnPickerOpen) return; const path = e.composedPath(); const picker = this.shadowRoot?.querySelector('.column-picker-wrapper'); if (picker && !path.includes(picker)) { this._columnPickerOpen = false; } }; this._handleResizeMove = (e) => { if (!this._resizing) return; const col = this._def.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._handleClickOutsideColumnPicker); 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._handleClickOutsideColumnPicker); this._resizeObserver?.disconnect(); } willUpdate(changedProperties) { if (changedProperties.has('def')) { // Copy user's def and normalize action columns this._def = { ...this.def, columns: this.def.columns.map(col => { if (col.type === 'actions') { return { ...col, sticky: 'right', resizable: false }; } return { ...col }; }) }; this._displayedColumns = this._def.displayedColumns || this._def.columns.map(c => c.id); this._fetch(); this._initRefresh(); } } updated(changedProperties) { this._updateScrollFlags(); } // ---------------------------------------------------------------------------- // 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 // ---------------------------------------------------------------------------- /** * 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._def.dataSource) return; this._dataState = 'loading'; // Build request based on mode let request; switch (this._def.dataSource.mode) { case 'opensearch': throw Error('Opensearch not supported yet'); case 'db': throw Error('DB not supported yet'); default: // solr request = { page: this._page - 1, size: this._pageSize, sorts: [], filterFields: [], queryFields: [], facetFields: [] }; if (this._searchQuery?.trim().length) { request.queryFields.push({ name: '_text_', operation: 'IS', value: escapeSolrQuery(this._searchQuery) }); } } this._def.dataSource.fetch(request) .then(response => { // Parse response based on mode switch (this._def.dataSource?.mode) { case 'opensearch': { throw Error('Opensearch not supported yet'); break; } case 'db': { throw Error('DB not supported yet'); 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._dataState = 'success'; this._updateSearchPosition(); }) .catch(err => { this._dataState = 'error'; KRSnackbar.show({ message: err instanceof Error ? err.message : 'Failed to load data', type: 'error' }); }); } /** * 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._def.refreshInterval && this._def.refreshInterval > 0) { this._refreshTimer = window.setInterval(() => { this._fetch(); }, this._def.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._displayedColumns.includes(columnId)) { this._displayedColumns = this._displayedColumns.filter(id => id !== columnId); } else { this._displayedColumns = [...this._displayedColumns, columnId]; } } // 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._displayedColumns .map(id => this._def.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) { this.dispatchEvent(new CustomEvent('action', { detail: { action: action.id }, bubbles: true, composed: true })); } // ---------------------------------------------------------------------------- // Rendering // ---------------------------------------------------------------------------- _renderCellContent(column, row) { const value = row[column.id]; if (column.render) { const result = column.render(row); // If render returns a string, treat it as HTML return typeof result === 'string' ? unsafeHTML(result) : result; } if (value === null || value === undefined) { return ''; } switch (column.type) { case 'number': return typeof value === 'number' ? value.toLocaleString() : String(value); case 'currency': return typeof value === 'number' ? value.toLocaleString('en-US', { style: 'currency', currency: 'USD' }) : String(value); case 'date': return value instanceof Date ? value.toLocaleDateString() : new Date(value).toLocaleDateString(); 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._def.title && !this._def.actions?.length && this._totalPages <= 1) { return nothing; } return html ` <div class=\"header\"> <div class=\"title\">${this._def.title ?? ''}</div> <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._def.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._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._def.actions?.length === 1 ? html ` <kr-button class=\"actions\" @click=${() => this._handleAction(this._def.actions[0])} > ${this._def.actions[0].label} </kr-button> ` : this._def.actions?.length ? html ` <kr-button class=\"actions\" .options=${this._def.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; } /** 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._data.map(row => html ` <div class=\"row\"> ${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)} </div> `)} </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._def.columns.length) { return html `<slot></slot>`; } return html ` ${this._renderHeader()} ${this._renderTable()} `; } }"
1230
1230
  }
1231
1231
  ],
1232
1232
  "exports": [
@@ -1537,7 +1537,7 @@
1537
1537
  "kind": "field",
1538
1538
  "name": "color",
1539
1539
  "type": {
1540
- "text": "'primary' | 'secondary'"
1540
+ "text": "'primary' | 'secondary' | 'danger'"
1541
1541
  },
1542
1542
  "default": "'primary'",
1543
1543
  "description": "The button color",
@@ -1735,7 +1735,7 @@
1735
1735
  {
1736
1736
  "name": "color",
1737
1737
  "type": {
1738
- "text": "'primary' | 'secondary'"
1738
+ "text": "'primary' | 'secondary' | 'danger'"
1739
1739
  },
1740
1740
  "default": "'primary'",
1741
1741
  "description": "The button color",
@@ -2275,18 +2275,18 @@
2275
2275
  },
2276
2276
  {
2277
2277
  "kind": "js",
2278
- "name": "KRSelect",
2278
+ "name": "KRSelectField",
2279
2279
  "declaration": {
2280
- "name": "KRSelect",
2281
- "module": "./select/select.js"
2280
+ "name": "KRSelectField",
2281
+ "module": "./select-field/select-field.js"
2282
2282
  }
2283
2283
  },
2284
2284
  {
2285
2285
  "kind": "js",
2286
- "name": "KRSelectOption",
2286
+ "name": "KRSelectFieldOption",
2287
2287
  "declaration": {
2288
- "name": "KRSelectOption",
2289
- "module": "./select/select-option.js"
2288
+ "name": "KRSelectFieldOption",
2289
+ "module": "./select-field/select-field-option.js"
2290
2290
  }
2291
2291
  }
2292
2292
  ]
@@ -2465,6 +2465,28 @@
2465
2465
  }
2466
2466
  ]
2467
2467
  },
2468
+ {
2469
+ "kind": "javascript-module",
2470
+ "path": "src/style/base.ts",
2471
+ "declarations": [
2472
+ {
2473
+ "kind": "variable",
2474
+ "name": "krBaseCSS",
2475
+ "default": "css` :host, *, *::before, *::after { box-sizing: border-box; } :host { /* Primary */ --kr-primary: rgb(22, 48, 82); --kr-primary-hover: rgb(16, 36, 62); --kr-primary-text: #ffffff; /* Accent */ --kr-accent: #BEEA4E; --kr-accent-hover: #a8d43a; --kr-accent-text: #000000; /* Text */ --kr-text: #000000; --kr-text-muted: #4b5563; --kr-text-disabled: #9ca3af; /* Borders */ --kr-border: #e5e7eb; } `",
2476
+ "description": "Base theme styles with CSS custom properties.\nImport into components: static styles = [krBaseCSS, css`...`]"
2477
+ }
2478
+ ],
2479
+ "exports": [
2480
+ {
2481
+ "kind": "js",
2482
+ "name": "krBaseCSS",
2483
+ "declaration": {
2484
+ "name": "krBaseCSS",
2485
+ "module": "src/style/base.ts"
2486
+ }
2487
+ }
2488
+ ]
2489
+ },
2468
2490
  {
2469
2491
  "kind": "javascript-module",
2470
2492
  "path": "src/table/table.ts",
@@ -2600,15 +2622,6 @@
2600
2622
  "privacy": "private",
2601
2623
  "default": "[]"
2602
2624
  },
2603
- {
2604
- "kind": "field",
2605
- "name": "_widthsLocked",
2606
- "type": {
2607
- "text": "boolean"
2608
- },
2609
- "privacy": "private",
2610
- "default": "false"
2611
- },
2612
2625
  {
2613
2626
  "kind": "field",
2614
2627
  "name": "_resizing",
@@ -2705,32 +2718,13 @@
2705
2718
  },
2706
2719
  {
2707
2720
  "kind": "method",
2708
- "name": "_measureTextWidth",
2721
+ "name": "_getGridTemplateColumns",
2709
2722
  "privacy": "private",
2710
2723
  "return": {
2711
2724
  "type": {
2712
- "text": "number"
2713
- }
2714
- },
2715
- "parameters": [
2716
- {
2717
- "name": "text",
2718
- "type": {
2719
- "text": "string"
2720
- }
2721
- },
2722
- {
2723
- "name": "font",
2724
- "type": {
2725
- "text": "string"
2726
- }
2725
+ "text": "string"
2727
2726
  }
2728
- ]
2729
- },
2730
- {
2731
- "kind": "method",
2732
- "name": "_lockColumnWidths",
2733
- "privacy": "private"
2727
+ }
2734
2728
  },
2735
2729
  {
2736
2730
  "kind": "method",
@@ -2944,16 +2938,6 @@
2944
2938
  "privacy": "private",
2945
2939
  "description": "Renders status message (loading, error, empty)"
2946
2940
  },
2947
- {
2948
- "kind": "method",
2949
- "name": "_getGridTemplateColumns",
2950
- "privacy": "private",
2951
- "return": {
2952
- "type": {
2953
- "text": "string"
2954
- }
2955
- }
2956
- },
2957
2941
  {
2958
2942
  "kind": "method",
2959
2943
  "name": "_renderTable",
@@ -3006,28 +2990,6 @@
3006
2990
  }
3007
2991
  ]
3008
2992
  },
3009
- {
3010
- "kind": "javascript-module",
3011
- "path": "src/style/base.ts",
3012
- "declarations": [
3013
- {
3014
- "kind": "variable",
3015
- "name": "krBaseCSS",
3016
- "default": "css` :host, *, *::before, *::after { box-sizing: border-box; } :host { /* Primary */ --kr-primary: rgb(22, 48, 82); --kr-primary-hover: rgb(16, 36, 62); --kr-primary-text: #ffffff; /* Accent */ --kr-accent: #BEEA4E; --kr-accent-hover: #a8d43a; --kr-accent-text: #000000; /* Text */ --kr-text: #000000; --kr-text-muted: #4b5563; --kr-text-disabled: #9ca3af; /* Borders */ --kr-border: #e5e7eb; } `",
3017
- "description": "Base theme styles with CSS custom properties.\nImport into components: static styles = [krBaseCSS, css`...`]"
3018
- }
3019
- ],
3020
- "exports": [
3021
- {
3022
- "kind": "js",
3023
- "name": "krBaseCSS",
3024
- "declaration": {
3025
- "name": "krBaseCSS",
3026
- "module": "src/style/base.ts"
3027
- }
3028
- }
3029
- ]
3030
- },
3031
2993
  {
3032
2994
  "kind": "javascript-module",
3033
2995
  "path": "src/tabs/tab.ts",
@@ -3880,56 +3842,56 @@
3880
3842
  },
3881
3843
  {
3882
3844
  "kind": "javascript-module",
3883
- "path": "dist/form/select/select-option.js",
3845
+ "path": "dist/form/select-field/select-field-option.js",
3884
3846
  "declarations": [
3885
3847
  {
3886
3848
  "kind": "variable",
3887
- "name": "KRSelectOption",
3888
- "default": "class KRSelectOption extends LitElement { constructor() { super(...arguments); /** * The option value */ this.value = ''; /** * Whether the option is disabled */ this.disabled = false; } /** Gets the label text from the slot */ get label() { return this.textContent?.trim() || ''; } render() { return html `<slot></slot>`; } }",
3889
- "description": "An option for the kr-select component."
3849
+ "name": "KRSelectFieldOption",
3850
+ "default": "class KRSelectFieldOption extends LitElement { constructor() { super(...arguments); /** * The option value */ this.value = ''; /** * Whether the option is disabled */ this.disabled = false; } /** Gets the label text from the slot */ get label() { return this.textContent?.trim() || ''; } render() { return html `<slot></slot>`; } }",
3851
+ "description": "An option for the kr-select-field component."
3890
3852
  }
3891
3853
  ],
3892
3854
  "exports": [
3893
3855
  {
3894
3856
  "kind": "js",
3895
- "name": "KRSelectOption",
3857
+ "name": "KRSelectFieldOption",
3896
3858
  "declaration": {
3897
- "name": "KRSelectOption",
3898
- "module": "dist/form/select/select-option.js"
3859
+ "name": "KRSelectFieldOption",
3860
+ "module": "dist/form/select-field/select-field-option.js"
3899
3861
  }
3900
3862
  }
3901
3863
  ]
3902
3864
  },
3903
3865
  {
3904
3866
  "kind": "javascript-module",
3905
- "path": "dist/form/select/select.js",
3867
+ "path": "dist/form/select-field/select-field.js",
3906
3868
  "declarations": [
3907
3869
  {
3908
3870
  "kind": "variable",
3909
- "name": "KRSelect",
3910
- "default": "class KRSelect extends LitElement { constructor() { super(); /** * The select label text */ this.label = ''; /** * The input name for form submission */ this.name = ''; /** * The currently selected value */ this.value = ''; /** * Placeholder text when no option is selected */ this.placeholder = 'Select an option'; /** * Whether the select is disabled */ this.disabled = false; /** * Whether the field is required */ this.required = false; /** * Whether the field is readonly */ this.readonly = false; /** * Helper text shown below the select */ this.hint = ''; this._isOpen = false; this._highlightedIndex = -1; this._touched = false; this._handleInvalid = (e) => { e.preventDefault(); this._touched = true; }; this._handleOutsideClick = (e) => { if (!this.contains(e.target)) { this._close(); } }; this._handleKeyDown = (e) => { if (!this._isOpen) return; const options = Array.from(this.querySelectorAll('kr-select-option')); switch (e.key) { case 'Escape': this._close(); this._triggerElement?.focus(); break; case 'ArrowDown': e.preventDefault(); if (options.some(o => !o.disabled)) { let newIndex = this._highlightedIndex + 1; while (newIndex < options.length && options[newIndex]?.disabled) newIndex++; if (newIndex < options.length) this._highlightedIndex = newIndex; } break; case 'ArrowUp': e.preventDefault(); { let newIndex = this._highlightedIndex - 1; while (newIndex >= 0 && options[newIndex]?.disabled) newIndex--; if (newIndex >= 0) this._highlightedIndex = newIndex; } break; case 'Enter': e.preventDefault(); if (this._highlightedIndex >= 0 && this._highlightedIndex < options.length) { this._selectOption(options[this._highlightedIndex]); } break; } }; this._internals = this.attachInternals(); } // Form-associated custom element callbacks 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 = false; this._internals.setFormValue(''); this._internals.setValidity({}); } formStateRestoreCallback(state) { this.value = state; } connectedCallback() { super.connectedCallback(); document.addEventListener('click', this._handleOutsideClick); document.addEventListener('keydown', this._handleKeyDown); this.addEventListener('invalid', this._handleInvalid); } firstUpdated() { this._updateValidity(); } updated(changedProperties) { if (changedProperties.has('required') || changedProperties.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) return; if (this._isOpen) { this._close(); } else { this._isOpen = true; const options = Array.from(this.querySelectorAll('kr-select-option')); this._highlightedIndex = options.findIndex(o => o.value === this.value); } } _close() { this._isOpen = false; this._highlightedIndex = -1; } _selectOption(option) { if (option.disabled) return; this.value = option.value; this._internals.setFormValue(this.value); this._updateValidity(); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); this._close(); this._triggerElement?.focus(); } _handleBlur() { this._touched = true; this._updateValidity(); } _updateValidity() { if (this.required && !this.value) { this._internals.setValidity({ valueMissing: true }, 'Please select an option', this._triggerElement); } else { this._internals.setValidity({}); } } render() { const options = Array.from(this.querySelectorAll('kr-select-option')); const selectedLabel = options.find(o => o.value === this.value)?.label; return html ` <div class=\"wrapper\"> ${this.label ? html ` <label> ${this.label} ${this.required ? html `<span class=\"required\" aria-hidden=\"true\">*</span>` : ''} </label> ` : nothing} <div class=\"select-wrapper\"> <button class=${classMap({ 'select-trigger': true, '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=${classMap({ 'select-value': true, 'select-placeholder': !selectedLabel })}> ${selectedLabel || this.placeholder} </span> <svg class=${classMap({ 'chevron-icon': true, 'select-icon': true, 'select-icon--open': this._isOpen })} viewBox=\"0 0 20 20\" fill=\"currentColor\" > <path fill-rule=\"evenodd\" d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\" clip-rule=\"evenodd\" /> </svg> </button> <div class=${classMap({ 'select-dropdown': true, 'hidden': !this._isOpen })} role=\"listbox\"> <div class=\"select-options\"> ${options.length === 0 ? html `<div class=\"select-empty\">No options available</div>` : options.map((option, idx) => { const isSelected = option.value === this.value; return html ` <div class=${classMap({ 'select-option': true, 'select-option--selected': isSelected, 'select-option--disabled': option.disabled, 'select-option--highlighted': idx === this._highlightedIndex, })} role=\"option\" aria-selected=${isSelected} @click=${() => this._selectOption(option)} @mouseenter=${() => (this._highlightedIndex = idx)} > ${option.label} ${isSelected ? html `<svg class=\"chevron-icon select-check\" viewBox=\"0 0 20 20\" fill=\"currentColor\"> <path fill-rule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clip-rule=\"evenodd\" /> </svg>` : nothing} </div> `; })} </div> </div> </div> ${this._touched && this.required && !this.value ? html `<div class=\"validation-message\">Please select an option</div>` : this.hint ? html `<div class=\"hint\">${this.hint}</div>` : ''} </div> <div class=\"options-slot\"> <slot @slotchange=${() => this.requestUpdate()}></slot> </div> `; } // Public methods for programmatic control focus() { this._triggerElement?.focus(); } blur() { this._triggerElement?.blur(); } }",
3871
+ "name": "KRSelectField",
3872
+ "default": "class KRSelectField extends LitElement { constructor() { super(); /** * The select label text */ this.label = ''; /** * The input name for form submission */ this.name = ''; /** * The currently selected value */ this.value = ''; /** * Placeholder text when no option is selected */ this.placeholder = 'Select an option'; /** * Whether the select is disabled */ this.disabled = false; /** * Whether the field is required */ this.required = false; /** * Whether the field is readonly */ this.readonly = false; /** * Helper text shown below the select */ this.hint = ''; this._isOpen = false; this._highlightedIndex = -1; this._touched = false; this._handleInvalid = (e) => { e.preventDefault(); this._touched = true; }; this._handleOutsideClick = (e) => { if (!e.composedPath().includes(this)) { this._close(); } }; this._handleKeyDown = (e) => { if (!this._isOpen) return; const options = Array.from(this.querySelectorAll('kr-select-field-option')); switch (e.key) { case 'Escape': this._close(); this._triggerElement?.focus(); break; case 'ArrowDown': e.preventDefault(); if (options.some(o => !o.disabled)) { let newIndex = this._highlightedIndex + 1; while (newIndex < options.length && options[newIndex]?.disabled) newIndex++; if (newIndex < options.length) this._highlightedIndex = newIndex; } break; case 'ArrowUp': e.preventDefault(); { let newIndex = this._highlightedIndex - 1; while (newIndex >= 0 && options[newIndex]?.disabled) newIndex--; if (newIndex >= 0) this._highlightedIndex = newIndex; } break; case 'Enter': e.preventDefault(); if (this._highlightedIndex >= 0 && this._highlightedIndex < options.length) { this._selectOption(options[this._highlightedIndex]); } break; } }; this._internals = this.attachInternals(); } // Form-associated custom element callbacks 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 = false; this._internals.setFormValue(''); this._internals.setValidity({}); } formStateRestoreCallback(state) { this.value = state; } connectedCallback() { super.connectedCallback(); document.addEventListener('click', this._handleOutsideClick); document.addEventListener('keydown', this._handleKeyDown); this.addEventListener('invalid', this._handleInvalid); } firstUpdated() { this._updateValidity(); } updated(changedProperties) { if (changedProperties.has('required') || changedProperties.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) return; if (this._isOpen) { this._close(); } else { this._isOpen = true; const options = Array.from(this.querySelectorAll('kr-select-field-option')); this._highlightedIndex = options.findIndex(o => o.value === this.value); } } _close() { this._isOpen = false; this._highlightedIndex = -1; } _selectOption(option) { if (option.disabled) return; this.value = option.value; this._internals.setFormValue(this.value); this._updateValidity(); this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); this._close(); this._triggerElement?.focus(); } _handleBlur() { this._touched = true; this._updateValidity(); } _updateValidity() { if (this.required && !this.value) { this._internals.setValidity({ valueMissing: true }, 'Please select an option', this._triggerElement); } else { this._internals.setValidity({}); } } render() { const options = Array.from(this.querySelectorAll('kr-select-field-option')); const selectedLabel = options.find(o => o.value === this.value)?.label; return html ` <div class=\"wrapper\"> ${this.label ? html ` <label> ${this.label} ${this.required ? html `<span class=\"required\" aria-hidden=\"true\">*</span>` : ''} </label> ` : nothing} <div class=\"select-wrapper\"> <button class=${classMap({ 'select-trigger': true, '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=${classMap({ 'select-value': true, 'select-placeholder': !selectedLabel })}> ${selectedLabel || this.placeholder} </span> <svg class=${classMap({ 'chevron-icon': true, 'select-icon': true, 'select-icon--open': this._isOpen })} viewBox=\"0 0 20 20\" fill=\"currentColor\" > <path fill-rule=\"evenodd\" d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\" clip-rule=\"evenodd\" /> </svg> </button> <div class=${classMap({ 'select-dropdown': true, 'hidden': !this._isOpen })} role=\"listbox\"> <div class=\"select-options\"> ${options.length === 0 ? html `<div class=\"select-empty\">No options available</div>` : options.map((option, idx) => { const isSelected = option.value === this.value; return html ` <div class=${classMap({ 'select-option': true, 'select-option--selected': isSelected, 'select-option--disabled': option.disabled, 'select-option--highlighted': idx === this._highlightedIndex, })} role=\"option\" aria-selected=${isSelected} @click=${() => this._selectOption(option)} @mouseenter=${() => (this._highlightedIndex = idx)} > ${option.label} ${isSelected ? html `<svg class=\"chevron-icon select-check\" viewBox=\"0 0 20 20\" fill=\"currentColor\"> <path fill-rule=\"evenodd\" d=\"M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z\" clip-rule=\"evenodd\" /> </svg>` : nothing} </div> `; })} </div> </div> </div> ${this._touched && this.required && !this.value ? html `<div class=\"validation-message\">Please select an option</div>` : this.hint ? html `<div class=\"hint\">${this.hint}</div>` : ''} </div> <div class=\"options-slot\"> <slot @slotchange=${() => this.requestUpdate()}></slot> </div> `; } // Public methods for programmatic control focus() { this._triggerElement?.focus(); } blur() { this._triggerElement?.blur(); } }",
3911
3873
  "description": "A select dropdown component that works with native browser forms.\n\nUses ElementInternals for form association, allowing the component\nto participate in form submission, validation, and reset."
3912
3874
  }
3913
3875
  ],
3914
3876
  "exports": [
3915
3877
  {
3916
3878
  "kind": "js",
3917
- "name": "KRSelect",
3879
+ "name": "KRSelectField",
3918
3880
  "declaration": {
3919
- "name": "KRSelect",
3920
- "module": "dist/form/select/select.js"
3881
+ "name": "KRSelectField",
3882
+ "module": "dist/form/select-field/select-field.js"
3921
3883
  }
3922
3884
  }
3923
3885
  ]
3924
3886
  },
3925
3887
  {
3926
3888
  "kind": "javascript-module",
3927
- "path": "src/form/select/select-option.ts",
3889
+ "path": "src/form/select-field/select-field-option.ts",
3928
3890
  "declarations": [
3929
3891
  {
3930
3892
  "kind": "class",
3931
- "description": "An option for the kr-select component.",
3932
- "name": "KRSelectOption",
3893
+ "description": "An option for the kr-select-field component.",
3894
+ "name": "KRSelectFieldOption",
3933
3895
  "slots": [
3934
3896
  {
3935
3897
  "description": "The option label content",
@@ -3991,40 +3953,40 @@
3991
3953
  "name": "LitElement",
3992
3954
  "package": "lit"
3993
3955
  },
3994
- "tagName": "kr-select-option",
3956
+ "tagName": "kr-select-field-option",
3995
3957
  "customElement": true
3996
3958
  }
3997
3959
  ],
3998
3960
  "exports": [
3999
3961
  {
4000
3962
  "kind": "js",
4001
- "name": "KRSelectOption",
3963
+ "name": "KRSelectFieldOption",
4002
3964
  "declaration": {
4003
- "name": "KRSelectOption",
4004
- "module": "src/form/select/select-option.ts"
3965
+ "name": "KRSelectFieldOption",
3966
+ "module": "src/form/select-field/select-field-option.ts"
4005
3967
  }
4006
3968
  },
4007
3969
  {
4008
3970
  "kind": "custom-element-definition",
4009
- "name": "kr-select-option",
3971
+ "name": "kr-select-field-option",
4010
3972
  "declaration": {
4011
- "name": "KRSelectOption",
4012
- "module": "src/form/select/select-option.ts"
3973
+ "name": "KRSelectFieldOption",
3974
+ "module": "src/form/select-field/select-field-option.ts"
4013
3975
  }
4014
3976
  }
4015
3977
  ]
4016
3978
  },
4017
3979
  {
4018
3980
  "kind": "javascript-module",
4019
- "path": "src/form/select/select.ts",
3981
+ "path": "src/form/select-field/select-field.ts",
4020
3982
  "declarations": [
4021
3983
  {
4022
3984
  "kind": "class",
4023
3985
  "description": "A select dropdown component that works with native browser forms.\n\nUses ElementInternals for form association, allowing the component\nto participate in form submission, validation, and reset.",
4024
- "name": "KRSelect",
3986
+ "name": "KRSelectField",
4025
3987
  "slots": [
4026
3988
  {
4027
- "description": "The kr-select-option elements",
3989
+ "description": "The kr-select-field-option elements",
4028
3990
  "name": ""
4029
3991
  }
4030
3992
  ],
@@ -4238,7 +4200,7 @@
4238
4200
  {
4239
4201
  "name": "option",
4240
4202
  "type": {
4241
- "text": "KRSelectOption"
4203
+ "text": "KRSelectFieldOption"
4242
4204
  }
4243
4205
  }
4244
4206
  ]
@@ -4348,25 +4310,25 @@
4348
4310
  "name": "LitElement",
4349
4311
  "package": "lit"
4350
4312
  },
4351
- "tagName": "kr-select",
4313
+ "tagName": "kr-select-field",
4352
4314
  "customElement": true
4353
4315
  }
4354
4316
  ],
4355
4317
  "exports": [
4356
4318
  {
4357
4319
  "kind": "js",
4358
- "name": "KRSelect",
4320
+ "name": "KRSelectField",
4359
4321
  "declaration": {
4360
- "name": "KRSelect",
4361
- "module": "src/form/select/select.ts"
4322
+ "name": "KRSelectField",
4323
+ "module": "src/form/select-field/select-field.ts"
4362
4324
  }
4363
4325
  },
4364
4326
  {
4365
4327
  "kind": "custom-element-definition",
4366
- "name": "kr-select",
4328
+ "name": "kr-select-field",
4367
4329
  "declaration": {
4368
- "name": "KRSelect",
4369
- "module": "src/form/select/select.ts"
4330
+ "name": "KRSelectField",
4331
+ "module": "src/form/select-field/select-field.ts"
4370
4332
  }
4371
4333
  }
4372
4334
  ]