@kodaris/krubble-components 1.0.10 → 1.0.11

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.
@@ -162,7 +162,7 @@
162
162
  {
163
163
  "kind": "variable",
164
164
  "name": "KRButton",
165
- "default": "class KRButton extends i$2 { 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._dropdownOpen = 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._dropdownOpen) { this._dropdownOpen = false; } }; this._handleClickOutside = (e) => { if (this._dropdownOpen && !this.contains(e.target)) { this._dropdownOpen = 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._dropdownOpen = !this._dropdownOpen; } _handleOptionClick(option, e) { e.stopPropagation(); this._dropdownOpen = 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() { const hasOptions = this.options.length > 0; return b ` <slot></slot> ${hasOptions ? b `<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>` : A} ${this._state !== 'idle' ? b `<span class=\"state-overlay\"> ${this._state === 'loading' ? b `<span class=\"spinner\"></span>` : this._stateText} </span>` : A} ${hasOptions ? b ` <div class=\"dropdown ${this._dropdownOpen ? 'open' : ''}\"> ${this.options.map(option => b ` <button class=\"dropdown-item\" @click=${(e) => this._handleOptionClick(option, e)} >${option.label}</button> `)} </div> ` : A} `; } }",
165
+ "default": "class KRButton extends i$2 { 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 b ` <slot></slot> ${this.options.length ? b `<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>` : A} ${this._state !== 'idle' ? b `<span class=\"state-overlay\"> ${this._state === 'loading' ? b `<span class=\"spinner\"></span>` : this._stateText} </span>` : A} ${this.options.length ? b ` <div class=\"dropdown ${this._dropdownOpened ? 'dropdown--opened' : ''} ${this._dropdownAlignRight ? 'dropdown--align-right' : ''}\"> ${this.options.map(option => b ` <button class=\"dropdown-item\" @click=${(e) => this._handleOptionClick(option, e)} >${option.label}</button> `)} </div> ` : A} `; } }",
166
166
  "description": "A customizable button component."
167
167
  },
168
168
  {
@@ -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 = 'edge'; 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._columnWidths = new Map(); this._resizing = null; this._resizeObserver = null; this._searchPositionLocked = false; 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; this._columnWidths.set(this._resizing.columnId, Math.max(50, this._resizing.startWidth + (e.clientX - this._resizing.startX))); 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(); } updated(changedProperties) { if (changedProperties.has('def')) { this._displayedColumns = this.def.displayedColumns || this.def.columns.map(c => c.id); this._fetch(); this._initRefresh(); } 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(); } /** * 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. getDisplayedColumns() { return this._displayedColumns.map(id => this.def.columns.find(col => col.id === id)); } // ---------------------------------------------------------------------------- // 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); } // ---------------------------------------------------------------------------- // Column Resizing // ---------------------------------------------------------------------------- _handleResizeStart(e, columnId) { e.preventDefault(); const column = this.def.columns.find(c => c.id === columnId); this._resizing = { columnId, startX: e.clientX, startWidth: this._columnWidths.get(columnId) || parseInt(column?.width || '150', 10) }; 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) { return column.render(row); } 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 = {}; const isLastColumn = index === this.getDisplayedColumns().length - 1; const customWidth = this._columnWidths.get(column.id); const width = customWidth ? `${customWidth}px` : (column.width || '150px'); if (isLastColumn && !customWidth) { styles.flex = '1'; styles.minWidth = column.width || '150px'; styles.marginRight = '24px'; } else { styles.width = width; } if (column.minWidth) styles.minWidth = column.minWidth; if (column.sticky === 'left') { let leftOffset = 0; for (let i = 0; i < index; i++) { if (this.getDisplayedColumns()[i].sticky === 'left') { leftOffset += parseInt(this.getDisplayedColumns()[i].width || '150', 10); } } styles.left = `${leftOffset}px`; } if (column.sticky === 'right') { let rightOffset = 0; for (let i = index + 1; i < this.getDisplayedColumns().length; i++) { if (this.getDisplayedColumns()[i].sticky === 'right') { rightOffset += parseInt(this.getDisplayedColumns()[i].width || '150', 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].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 ? 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\"> <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))} > <span class=\"header-cell__label\">${col.label ?? col.id}</span> <div class=\"header-cell__resize\" @mousedown=${(e) => this._handleResizeStart(e, col.id)} ></div> </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))} > ${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._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()} `; } }"
260
260
  },
261
261
  {
262
262
  "kind": "variable",
@@ -447,12 +447,12 @@
447
447
  {
448
448
  "kind": "variable",
449
449
  "name": "Ee",
450
- "default": "class extends ne{constructor(){super(...arguments),this.variant=\"flat\",this.color=\"primary\",this.size=\"medium\",this.disabled=!1,this.options=[],this._state=\"idle\",this._stateText=\"\",this._dropdownOpen=!1,this._handleHostClick=e=>{this.options.length&&(e.stopPropagation(),this._toggleDropdown())},this._handleKeydown=e=>{\"Enter\"!==e.key&&\" \"!==e.key||(e.preventDefault(),this.options.length?this._toggleDropdown():this.click()),\"Escape\"===e.key&&this._dropdownOpen&&(this._dropdownOpen=!1)},this._handleClickOutside=e=>{this._dropdownOpen&&!this.contains(e.target)&&(this._dropdownOpen=!1)}}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._dropdownOpen=!this._dropdownOpen}_handleOptionClick(e,t){t.stopPropagation(),this._dropdownOpen=!1,this.dispatchEvent(new CustomEvent(\"option-select\",{detail:{id:e.id,label:e.label},bubbles:!0,composed:!0}))}showLoading(){this._clearStateTimeout(),this._state=\"loading\",this._stateText=\"\"}showSuccess(e=\"Success\",t=2e3){this._clearStateTimeout(),this._state=\"success\",this._stateText=e,this._stateTimeout=window.setTimeout(()=>this.reset(),t)}showError(e=\"Error\",t=2e3){this._clearStateTimeout(),this._state=\"error\",this._stateText=e,this._stateTimeout=window.setTimeout(()=>this.reset(),t)}reset(){this._clearStateTimeout(),this._state=\"idle\",this._stateText=\"\"}_clearStateTimeout(){this._stateTimeout&&(clearTimeout(this._stateTimeout),this._stateTimeout=void 0)}updated(e){this.classList.toggle(\"kr-button--loading\",\"loading\"===this._state),this.classList.toggle(\"kr-button--success\",\"success\"===this._state),this.classList.toggle(\"kr-button--error\",\"error\"===this._state),this.classList.toggle(`kr-button--${this.variant}`,!0),this.classList.toggle(`kr-button--${this.color}`,!0),this.classList.toggle(\"kr-button--small\",\"small\"===this.size),this.classList.toggle(\"kr-button--large\",\"large\"===this.size)}render(){const e=this.options.length>0;return U` <slot></slot> ${e?U`<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>`:V} ${\"idle\"!==this._state?U`<span class=\"state-overlay\"> ${\"loading\"===this._state?U`<span class=\"spinner\"></span>`:this._stateText} </span>`:V} ${e?U` <div class=\"dropdown ${this._dropdownOpen?\"open\":\"\"}\"> ${this.options.map(e=>U` <button class=\"dropdown-item\" @click=${t=>this._handleOptionClick(e,t)} >${e.label}</button> `)} </div> `:V} `}}"
450
+ "default": "class extends ne{constructor(){super(...arguments),this.variant=\"flat\",this.color=\"primary\",this.size=\"medium\",this.disabled=!1,this.options=[],this._state=\"idle\",this._stateText=\"\",this._dropdownOpened=!1,this._dropdownAlignRight=!1,this._handleHostClick=e=>{this.options.length&&(e.stopPropagation(),this._toggleDropdown())},this._handleKeydown=e=>{\"Enter\"!==e.key&&\" \"!==e.key||(e.preventDefault(),this.options.length?this._toggleDropdown():this.click()),\"Escape\"===e.key&&this._dropdownOpened&&(this._dropdownOpened=!1)},this._handleClickOutside=e=>{this._dropdownOpened&&!this.contains(e.target)&&(this._dropdownOpened=!1)}}connectedCallback(){super.connectedCallback(),this.setAttribute(\"role\",\"button\"),this.setAttribute(\"tabindex\",\"0\"),this.addEventListener(\"keydown\",this._handleKeydown),this.addEventListener(\"click\",this._handleHostClick),document.addEventListener(\"click\",this._handleClickOutside)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(\"keydown\",this._handleKeydown),this.removeEventListener(\"click\",this._handleHostClick),document.removeEventListener(\"click\",this._handleClickOutside)}_toggleDropdown(){this._dropdownOpened=!this._dropdownOpened,this._dropdownOpened&&requestAnimationFrame(()=>{const e=this.shadowRoot?.querySelector(\".dropdown\");if(e){const t=e.getBoundingClientRect();this._dropdownAlignRight=t.right>window.innerWidth}})}_handleOptionClick(e,t){t.stopPropagation(),this._dropdownOpened=!1,this.dispatchEvent(new CustomEvent(\"option-select\",{detail:{id:e.id,label:e.label},bubbles:!0,composed:!0}))}showLoading(){this._clearStateTimeout(),this._state=\"loading\",this._stateText=\"\"}showSuccess(e=\"Success\",t=2e3){this._clearStateTimeout(),this._state=\"success\",this._stateText=e,this._stateTimeout=window.setTimeout(()=>this.reset(),t)}showError(e=\"Error\",t=2e3){this._clearStateTimeout(),this._state=\"error\",this._stateText=e,this._stateTimeout=window.setTimeout(()=>this.reset(),t)}reset(){this._clearStateTimeout(),this._state=\"idle\",this._stateText=\"\"}_clearStateTimeout(){this._stateTimeout&&(clearTimeout(this._stateTimeout),this._stateTimeout=void 0)}updated(e){this.classList.toggle(\"kr-button--loading\",\"loading\"===this._state),this.classList.toggle(\"kr-button--success\",\"success\"===this._state),this.classList.toggle(\"kr-button--error\",\"error\"===this._state),this.classList.toggle(`kr-button--${this.variant}`,!0),this.classList.toggle(`kr-button--${this.color}`,!0),this.classList.toggle(\"kr-button--small\",\"small\"===this.size),this.classList.toggle(\"kr-button--large\",\"large\"===this.size)}render(){return U` <slot></slot> ${this.options.length?U`<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>`:V} ${\"idle\"!==this._state?U`<span class=\"state-overlay\"> ${\"loading\"===this._state?U`<span class=\"spinner\"></span>`:this._stateText} </span>`:V} ${this.options.length?U` <div class=\"dropdown ${this._dropdownOpened?\"dropdown--opened\":\"\"} ${this._dropdownAlignRight?\"dropdown--align-right\":\"\"}\"> ${this.options.map(e=>U` <button class=\"dropdown-item\" @click=${t=>this._handleOptionClick(e,t)} >${e.label}</button> `)} </div> `:V} `}}"
451
451
  },
452
452
  {
453
453
  "kind": "variable",
454
454
  "name": "ze",
455
- "default": "class extends ne{constructor(){super(...arguments),this.language=\"html\",this.code=\"\",this.activeTab=\"preview\",this.copied=!1}setTab(e){this.activeTab=e}getHighlightedCode(){return this.code?window.Prism&&window.Prism.languages[this.language]?window.Prism.highlight(this.code,window.Prism.languages[this.language],this.language):this.escapeHtml(this.code):\"\"}escapeHtml(e){const t=document.createElement(\"div\");return t.textContent=e,t.innerHTML}async copyCode(){if(this.code)try{await navigator.clipboard.writeText(this.code),this.copied=!0,setTimeout(()=>{this.copied=!1},2e3)}catch(e){console.error(\"Failed to copy code:\",e)}}render(){return U` <div class=\"tabs\"> <button class=${we({tab:!0,\"tab--active\":\"preview\"===this.activeTab})} @click=${()=>this.setTab(\"preview\")} > Preview </button> <button class=${we({tab:!0,\"tab--active\":\"code\"===this.activeTab})} @click=${()=>this.setTab(\"code\")} > Code </button> </div> <div class=${we({panel:!0,\"panel--active\":\"preview\"===this.activeTab,preview:!0})}> <slot name=\"preview\"></slot> </div> <div class=${we({panel:!0,\"panel--active\":\"code\"===this.activeTab,\"code-container\":!0})}> <button class=${we({\"copy-btn\":!0,\"copy-btn--copied\":this.copied})} @click=${this.copyCode} > ${this.copied?\"Copied!\":\"Copy\"} </button> <pre class=\"code\"><code>${Pe(this.getHighlightedCode())}</code></pre> </div> `}}"
455
+ "default": "class extends ne{constructor(){super(...arguments),this.language=\"html\",this.code=\"\",this.activeTab=\"preview\",this.copied=!1}setTab(e){this.activeTab=e}getHighlightedCode(){return this.code?window.Prism&&window.Prism.languages[this.language]?window.Prism.highlight(this.code,window.Prism.languages[this.language],this.language):this.escapeHtml(this.code):\"\"}escapeHtml(e){const t=document.createElement(\"div\");return t.textContent=e,t.innerHTML}async copyCode(){if(this.code)try{await navigator.clipboard.writeText(this.code),this.copied=!0,setTimeout(()=>{this.copied=!1},2e3)}catch(e){console.error(\"Failed to copy code:\",e)}}render(){return U` <div class=\"tabs\"> <button class=${we({tab:!0,\"tab--active\":\"preview\"===this.activeTab})} @click=${()=>this.setTab(\"preview\")} > Preview </button> <button class=${we({tab:!0,\"tab--active\":\"code\"===this.activeTab})} @click=${()=>this.setTab(\"code\")} > Code </button> </div> <div class=${we({panel:!0,\"panel--active\":\"preview\"===this.activeTab,preview:!0})}> <slot name=\"preview\"></slot> </div> <div class=${we({panel:!0,\"panel--active\":\"code\"===this.activeTab,\"code-container\":!0})}> <button class=${we({\"copy-btn\":!0,\"copy-btn--copied\":this.copied})} @click=${this.copyCode} > ${this.copied?\"Copied!\":\"Copy\"} </button> <pre class=\"code\"><code>${Oe(this.getHighlightedCode())}</code></pre> </div> `}}"
456
456
  },
457
457
  {
458
458
  "kind": "variable",
@@ -462,7 +462,7 @@
462
462
  {
463
463
  "kind": "class",
464
464
  "description": "",
465
- "name": "De",
465
+ "name": "Me",
466
466
  "members": [
467
467
  {
468
468
  "kind": "method",
@@ -511,8 +511,8 @@
511
511
  },
512
512
  {
513
513
  "kind": "variable",
514
- "name": "Me",
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> `}}"
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> `}}"
516
516
  },
517
517
  {
518
518
  "kind": "variable",
@@ -525,18 +525,18 @@
525
525
  },
526
526
  {
527
527
  "kind": "variable",
528
- "name": "Fe",
528
+ "name": "We",
529
529
  "default": "class extends ne{constructor(){super(...arguments),this.id=\"\",this.title=\"\",this.badge=\"\",this.badgeBackground=\"\",this.badgeColor=\"\",this.disabled=!1,this.dismissible=!1,this.active=!1}getIconElement(){return this.querySelector('[slot=\"icon\"]')}render(){return U`<slot></slot>`}}"
530
530
  },
531
531
  {
532
532
  "kind": "variable",
533
533
  "name": "Ye",
534
- "default": "class extends ne{constructor(){super(...arguments),this._scrollStyle=\"edge\",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._columnWidths=new Map,this._resizing=null,this._resizeObserver=null,this._searchPositionLocked=!1,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=>{this._resizing&&(this._columnWidths.set(this._resizing.columnId,Math.max(50,this._resizing.startWidth+(e.clientX-this._resizing.startX))),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()}updated(e){e.has(\"def\")&&(this._displayedColumns=this.def.displayedColumns||this.def.columns.map(e=>e.id),this._fetch(),this._initRefresh()),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()}_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))}_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)}_handleResizeStart(e,t){e.preventDefault();const i=this.def.columns.find(e=>e.id===t);this._resizing={columnId:t,startX:e.clientX,startWidth:this._columnWidths.get(t)||parseInt(i?.width||\"150\",10)},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)return e.render(t);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={},o=t===this.getDisplayedColumns().length-1,s=this._columnWidths.get(e.id),r=s?`${s}px`:e.width||\"150px\";if(o&&!s?(i.flex=\"1\",i.minWidth=e.width||\"150px\",i.marginRight=\"24px\"):i.width=r,e.minWidth&&(i.minWidth=e.minWidth),\"left\"===e.sticky){let e=0;for(let i=0;i<t;i++)\"left\"===this.getDisplayedColumns()[i].sticky&&(e+=parseInt(this.getDisplayedColumns()[i].width||\"150\",10));i.left=`${e}px`}if(\"right\"===e.sticky){let e=0;for(let i=t+1;i<this.getDisplayedColumns().length;i++)\"right\"===this.getDisplayedColumns()[i].sticky&&(e+=parseInt(this.getDisplayedColumns()[i].width||\"150\",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].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> ${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\"> <div class=\"header-row\"> ${this.getDisplayedColumns().map((e,t)=>U` <div class=${we(this._getHeaderCellClasses(e,t))} style=${Ue(this._getCellStyle(e,t))} > <span class=\"header-cell__label\">${e.label??e.id}</span> <div class=\"header-cell__resize\" @mousedown=${t=>this._handleResizeStart(t,e.id)} ></div> </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))} > ${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._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>`}}"
535
535
  },
536
536
  {
537
537
  "kind": "variable",
538
538
  "name": "it",
539
- "default": "class extends ne{constructor(){super(),this.label=\"\",this.name=\"\",this.value=\"\",this.placeholder=\"\",this.type=\"text\",this.required=!1,this.disabled=!1,this.readonly=!1,this.autocomplete=\"\",this.hint=\"\",this._touched=!1,this._dirty=!1,this._handleInvalid=e=>{e.preventDefault(),this._touched=!0},this._internals=this.attachInternals()}get form(){return this._internals.form}get validity(){return this._internals.validity}get validationMessage(){return this._internals.validationMessage}get willValidate(){return this._internals.willValidate}checkValidity(){return this._internals.checkValidity()}reportValidity(){return this._internals.reportValidity()}formResetCallback(){this.value=\"\",this._touched=!1,this._dirty=!1,this._internals.setFormValue(\"\"),this._internals.setValidity({})}formStateRestoreCallback(e){this.value=e}connectedCallback(){super.connectedCallback(),this.addEventListener(\"invalid\",this._handleInvalid)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(\"invalid\",this._handleInvalid)}firstUpdated(){this._updateValidity()}updated(e){(e.has(\"required\")||e.has(\"value\"))&&this._updateValidity()}_updateValidity(){this._input&&this._internals.setValidity(this._input.validity,this._input.validationMessage)}_handleInput(e){this.value=e.target.value,this._dirty=!0,this._internals.setFormValue(this.value),this._internals.setValidity(this._input.validity,this._input.validationMessage)}_handleChange(e){this.value=e.target.value,this._internals.setFormValue(this.value)}_handleBlur(){this._touched=!0,this._internals.setValidity(this._input.validity,this._input.validationMessage)}render(){let e=\"\";return this._touched&&this._input&&!this._input.validity.valid&&(e=this._input.validationMessage),U` <div class=\"wrapper\"> ${this.label?U` <label for=\"input\"> ${this.label} ${this.required?U`<span class=\"required\" aria-hidden=\"true\">*</span>`:\"\"} </label> `:\"\"} <input id=\"input\" class=${we({\"input--invalid\":this._touched&&this._input&&!this._input.validity.valid})} type=${this.type} name=${this.name} .value=${et(this.value)} placeholder=${this.placeholder} ?required=${this.required} ?disabled=${this.disabled} ?readonly=${this.readonly} minlength=${Je(this.minlength)} maxlength=${Je(this.maxlength)} pattern=${Je(this.pattern)} autocomplete=${Je(this.autocomplete||void 0)} @input=${this._handleInput} @change=${this._handleChange} @blur=${this._handleBlur} /> ${e?U`<div class=\"validation-message\">${e}</div>`:this.hint?U`<div class=\"hint\">${this.hint}</div>`:\"\"} </div> `}focus(){this._input?.focus()}blur(){this._input?.blur()}select(){this._input?.select()}}"
539
+ "default": "class extends ne{constructor(){super(),this.label=\"\",this.name=\"\",this.value=\"\",this.placeholder=\"\",this.type=\"text\",this.required=!1,this.disabled=!1,this.readonly=!1,this.autocomplete=\"\",this.hint=\"\",this._touched=!1,this._dirty=!1,this._handleInvalid=e=>{e.preventDefault(),this._touched=!0},this._internals=this.attachInternals()}get form(){return this._internals.form}get validity(){return this._internals.validity}get validationMessage(){return this._internals.validationMessage}get willValidate(){return this._internals.willValidate}checkValidity(){return this._internals.checkValidity()}reportValidity(){return this._internals.reportValidity()}formResetCallback(){this.value=\"\",this._touched=!1,this._dirty=!1,this._internals.setFormValue(\"\"),this._internals.setValidity({})}formStateRestoreCallback(e){this.value=e}connectedCallback(){super.connectedCallback(),this.addEventListener(\"invalid\",this._handleInvalid)}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(\"invalid\",this._handleInvalid)}firstUpdated(){this._updateValidity()}updated(e){(e.has(\"required\")||e.has(\"value\"))&&this._updateValidity()}_updateValidity(){this._input&&this._internals.setValidity(this._input.validity,this._input.validationMessage)}_handleInput(e){this.value=e.target.value,this._dirty=!0,this._internals.setFormValue(this.value),this._internals.setValidity(this._input.validity,this._input.validationMessage)}_handleChange(e){this.value=e.target.value,this._internals.setFormValue(this.value)}_handleBlur(){this._touched=!0,this._internals.setValidity(this._input.validity,this._input.validationMessage)}render(){let e=\"\";return this._touched&&this._input&&!this._input.validity.valid&&(e=this._input.validationMessage),U` <div class=\"wrapper\"> ${this.label?U` <label for=\"input\"> ${this.label} ${this.required?U`<span class=\"required\" aria-hidden=\"true\">*</span>`:\"\"} </label> `:\"\"} <input id=\"input\" class=${we({\"input--invalid\":this._touched&&this._input&&!this._input.validity.valid})} type=${this.type} name=${this.name} .value=${et(this.value)} placeholder=${this.placeholder} ?required=${this.required} ?disabled=${this.disabled} ?readonly=${this.readonly} minlength=${Ge(this.minlength)} maxlength=${Ge(this.maxlength)} pattern=${Ge(this.pattern)} autocomplete=${Ge(this.autocomplete||void 0)} @input=${this._handleInput} @change=${this._handleChange} @blur=${this._handleBlur} /> ${e?U`<div class=\"validation-message\">${e}</div>`:this.hint?U`<div class=\"hint\">${this.hint}</div>`:\"\"} </div> `}focus(){this._input?.focus()}blur(){this._input?.blur()}select(){this._input?.select()}}"
540
540
  },
541
541
  {
542
542
  "kind": "variable",
@@ -570,7 +570,7 @@
570
570
  "kind": "js",
571
571
  "name": "DialogRef",
572
572
  "declaration": {
573
- "name": "De",
573
+ "name": "Me",
574
574
  "module": "dist/krubble.bundled.min.js"
575
575
  }
576
576
  },
@@ -618,7 +618,7 @@
618
618
  "kind": "js",
619
619
  "name": "KRDialog",
620
620
  "declaration": {
621
- "name": "Me",
621
+ "name": "De",
622
622
  "module": "dist/krubble.bundled.min.js"
623
623
  }
624
624
  },
@@ -650,7 +650,7 @@
650
650
  "kind": "js",
651
651
  "name": "KRTab",
652
652
  "declaration": {
653
- "name": "Fe",
653
+ "name": "We",
654
654
  "module": "dist/krubble.bundled.min.js"
655
655
  }
656
656
  },
@@ -986,28 +986,6 @@
986
986
  }
987
987
  ]
988
988
  },
989
- {
990
- "kind": "javascript-module",
991
- "path": "dist/alert/alert.js",
992
- "declarations": [
993
- {
994
- "kind": "variable",
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
- }
999
- ],
1000
- "exports": [
1001
- {
1002
- "kind": "js",
1003
- "name": "KRAlert",
1004
- "declaration": {
1005
- "name": "KRAlert",
1006
- "module": "dist/alert/alert.js"
1007
- }
1008
- }
1009
- ]
1010
- },
1011
989
  {
1012
990
  "kind": "javascript-module",
1013
991
  "path": "dist/button/button.js",
@@ -1015,7 +993,7 @@
1015
993
  {
1016
994
  "kind": "variable",
1017
995
  "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._dropdownOpen = 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._dropdownOpen) { this._dropdownOpen = false; } }; this._handleClickOutside = (e) => { if (this._dropdownOpen && !this.contains(e.target)) { this._dropdownOpen = 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._dropdownOpen = !this._dropdownOpen; } _handleOptionClick(option, e) { e.stopPropagation(); this._dropdownOpen = 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() { const hasOptions = this.options.length > 0; return html ` <slot></slot> ${hasOptions ? 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} ${hasOptions ? html ` <div class=\"dropdown ${this._dropdownOpen ? 'open' : ''}\"> ${this.options.map(option => html ` <button class=\"dropdown-item\" @click=${(e) => this._handleOptionClick(option, e)} >${option.label}</button> `)} </div> ` : nothing} `; } }",
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} `; } }",
1019
997
  "description": "A customizable button component."
1020
998
  }
1021
999
  ],
@@ -1032,22 +1010,22 @@
1032
1010
  },
1033
1011
  {
1034
1012
  "kind": "javascript-module",
1035
- "path": "dist/context-menu/context-menu.js",
1013
+ "path": "dist/alert/alert.js",
1036
1014
  "declarations": [
1037
1015
  {
1038
1016
  "kind": "variable",
1039
- "name": "KRContextMenu",
1040
- "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> `; } }",
1041
- "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```"
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."
1042
1020
  }
1043
1021
  ],
1044
1022
  "exports": [
1045
1023
  {
1046
1024
  "kind": "js",
1047
- "name": "KRContextMenu",
1025
+ "name": "KRAlert",
1048
1026
  "declaration": {
1049
- "name": "KRContextMenu",
1050
- "module": "dist/context-menu/context-menu.js"
1027
+ "name": "KRAlert",
1028
+ "module": "dist/alert/alert.js"
1051
1029
  }
1052
1030
  }
1053
1031
  ]
@@ -1145,6 +1123,28 @@
1145
1123
  }
1146
1124
  ]
1147
1125
  },
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",
@@ -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 = 'edge'; 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._columnWidths = new Map(); this._resizing = null; this._resizeObserver = null; this._searchPositionLocked = false; 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; this._columnWidths.set(this._resizing.columnId, Math.max(50, this._resizing.startWidth + (e.clientX - this._resizing.startX))); 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(); } updated(changedProperties) { if (changedProperties.has('def')) { this._displayedColumns = this.def.displayedColumns || this.def.columns.map(c => c.id); this._fetch(); this._initRefresh(); } 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(); } /** * 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. getDisplayedColumns() { return this._displayedColumns.map(id => this.def.columns.find(col => col.id === id)); } // ---------------------------------------------------------------------------- // 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); } // ---------------------------------------------------------------------------- // Column Resizing // ---------------------------------------------------------------------------- _handleResizeStart(e, columnId) { e.preventDefault(); const column = this.def.columns.find(c => c.id === columnId); this._resizing = { columnId, startX: e.clientX, startWidth: this._columnWidths.get(columnId) || parseInt(column?.width || '150', 10) }; 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) { return column.render(row); } 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 = {}; const isLastColumn = index === this.getDisplayedColumns().length - 1; const customWidth = this._columnWidths.get(column.id); const width = customWidth ? `${customWidth}px` : (column.width || '150px'); if (isLastColumn && !customWidth) { styles.flex = '1'; styles.minWidth = column.width || '150px'; styles.marginRight = '24px'; } else { styles.width = width; } if (column.minWidth) styles.minWidth = column.minWidth; if (column.sticky === 'left') { let leftOffset = 0; for (let i = 0; i < index; i++) { if (this.getDisplayedColumns()[i].sticky === 'left') { leftOffset += parseInt(this.getDisplayedColumns()[i].width || '150', 10); } } styles.left = `${leftOffset}px`; } if (column.sticky === 'right') { let rightOffset = 0; for (let i = index + 1; i < this.getDisplayedColumns().length; i++) { if (this.getDisplayedColumns()[i].sticky === 'right') { rightOffset += parseInt(this.getDisplayedColumns()[i].width || '150', 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].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 ? 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\"> <div class=\"header-row\"> ${this.getDisplayedColumns().map((col, i) => html ` <div class=${classMap(this._getHeaderCellClasses(col, i))} style=${styleMap(this._getCellStyle(col, i))} > <span class=\"header-cell__label\">${col.label ?? col.id}</span> <div class=\"header-cell__resize\" @mousedown=${(e) => this._handleResizeStart(e, col.id)} ></div> </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))} > ${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._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()} `; } }"
1230
1230
  }
1231
1231
  ],
1232
1232
  "exports": [
@@ -1596,7 +1596,16 @@
1596
1596
  },
1597
1597
  {
1598
1598
  "kind": "field",
1599
- "name": "_dropdownOpen",
1599
+ "name": "_dropdownOpened",
1600
+ "type": {
1601
+ "text": "boolean"
1602
+ },
1603
+ "privacy": "private",
1604
+ "default": "false"
1605
+ },
1606
+ {
1607
+ "kind": "field",
1608
+ "name": "_dropdownAlignRight",
1600
1609
  "type": {
1601
1610
  "text": "boolean"
1602
1611
  },
@@ -2456,28 +2465,6 @@
2456
2465
  }
2457
2466
  ]
2458
2467
  },
2459
- {
2460
- "kind": "javascript-module",
2461
- "path": "src/style/base.ts",
2462
- "declarations": [
2463
- {
2464
- "kind": "variable",
2465
- "name": "krBaseCSS",
2466
- "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; } `",
2467
- "description": "Base theme styles with CSS custom properties.\nImport into components: static styles = [krBaseCSS, css`...`]"
2468
- }
2469
- ],
2470
- "exports": [
2471
- {
2472
- "kind": "js",
2473
- "name": "krBaseCSS",
2474
- "declaration": {
2475
- "name": "krBaseCSS",
2476
- "module": "src/style/base.ts"
2477
- }
2478
- }
2479
- ]
2480
- },
2481
2468
  {
2482
2469
  "kind": "javascript-module",
2483
2470
  "path": "src/table/table.ts",
@@ -2494,7 +2481,7 @@
2494
2481
  "text": "'overlay' | 'edge'"
2495
2482
  },
2496
2483
  "privacy": "private",
2497
- "default": "'edge'",
2484
+ "default": "'overlay'",
2498
2485
  "description": "Internal flag to switch between scroll edge modes:\n- 'overlay': Fixed padding with overlay elements that hide content at edges (scrollbar at viewport edge)\n- 'edge': Padding scrolls with content, allowing table to reach edges when scrolling"
2499
2486
  },
2500
2487
  {
@@ -2615,12 +2602,12 @@
2615
2602
  },
2616
2603
  {
2617
2604
  "kind": "field",
2618
- "name": "_columnWidths",
2605
+ "name": "_widthsLocked",
2619
2606
  "type": {
2620
- "text": "Map<string, number>"
2607
+ "text": "boolean"
2621
2608
  },
2622
2609
  "privacy": "private",
2623
- "default": "new Map()"
2610
+ "default": "false"
2624
2611
  },
2625
2612
  {
2626
2613
  "kind": "field",
@@ -2649,6 +2636,15 @@
2649
2636
  "privacy": "private",
2650
2637
  "default": "false"
2651
2638
  },
2639
+ {
2640
+ "kind": "field",
2641
+ "name": "_def",
2642
+ "type": {
2643
+ "text": "KRTableDef"
2644
+ },
2645
+ "privacy": "private",
2646
+ "default": "{ columns: [] }"
2647
+ },
2652
2648
  {
2653
2649
  "kind": "field",
2654
2650
  "name": "def",
@@ -2707,6 +2703,35 @@
2707
2703
  }
2708
2704
  ]
2709
2705
  },
2706
+ {
2707
+ "kind": "method",
2708
+ "name": "_measureTextWidth",
2709
+ "privacy": "private",
2710
+ "return": {
2711
+ "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
+ }
2727
+ }
2728
+ ]
2729
+ },
2730
+ {
2731
+ "kind": "method",
2732
+ "name": "_lockColumnWidths",
2733
+ "privacy": "private"
2734
+ },
2710
2735
  {
2711
2736
  "kind": "method",
2712
2737
  "name": "_updateSearchPosition",
@@ -2811,11 +2836,6 @@
2811
2836
  "kind": "method",
2812
2837
  "name": "_renderCellContent",
2813
2838
  "privacy": "private",
2814
- "return": {
2815
- "type": {
2816
- "text": "TemplateResult | string"
2817
- }
2818
- },
2819
2839
  "parameters": [
2820
2840
  {
2821
2841
  "name": "column",
@@ -2924,6 +2944,16 @@
2924
2944
  "privacy": "private",
2925
2945
  "description": "Renders status message (loading, error, empty)"
2926
2946
  },
2947
+ {
2948
+ "kind": "method",
2949
+ "name": "_getGridTemplateColumns",
2950
+ "privacy": "private",
2951
+ "return": {
2952
+ "type": {
2953
+ "text": "string"
2954
+ }
2955
+ }
2956
+ },
2927
2957
  {
2928
2958
  "kind": "method",
2929
2959
  "name": "_renderTable",
@@ -2976,6 +3006,28 @@
2976
3006
  }
2977
3007
  ]
2978
3008
  },
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
+ },
2979
3031
  {
2980
3032
  "kind": "javascript-module",
2981
3033
  "path": "src/tabs/tab.ts",
@@ -3384,50 +3436,6 @@
3384
3436
  }
3385
3437
  ]
3386
3438
  },
3387
- {
3388
- "kind": "javascript-module",
3389
- "path": "dist/form/select/select-option.js",
3390
- "declarations": [
3391
- {
3392
- "kind": "variable",
3393
- "name": "KRSelectOption",
3394
- "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>`; } }",
3395
- "description": "An option for the kr-select component."
3396
- }
3397
- ],
3398
- "exports": [
3399
- {
3400
- "kind": "js",
3401
- "name": "KRSelectOption",
3402
- "declaration": {
3403
- "name": "KRSelectOption",
3404
- "module": "dist/form/select/select-option.js"
3405
- }
3406
- }
3407
- ]
3408
- },
3409
- {
3410
- "kind": "javascript-module",
3411
- "path": "dist/form/select/select.js",
3412
- "declarations": [
3413
- {
3414
- "kind": "variable",
3415
- "name": "KRSelect",
3416
- "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(); } }",
3417
- "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."
3418
- }
3419
- ],
3420
- "exports": [
3421
- {
3422
- "kind": "js",
3423
- "name": "KRSelect",
3424
- "declaration": {
3425
- "name": "KRSelect",
3426
- "module": "dist/form/select/select.js"
3427
- }
3428
- }
3429
- ]
3430
- },
3431
3439
  {
3432
3440
  "kind": "javascript-module",
3433
3441
  "path": "dist/form/text-field/text-field.js",
@@ -3452,38 +3460,29 @@
3452
3460
  },
3453
3461
  {
3454
3462
  "kind": "javascript-module",
3455
- "path": "src/form/select/select-option.ts",
3463
+ "path": "src/form/text-field/text-field.ts",
3456
3464
  "declarations": [
3457
3465
  {
3458
3466
  "kind": "class",
3459
- "description": "An option for the kr-select component.",
3460
- "name": "KRSelectOption",
3461
- "slots": [
3462
- {
3463
- "description": "The option label content",
3464
- "name": ""
3465
- }
3466
- ],
3467
+ "description": "A text field component that works with native browser forms.\n\nUses ElementInternals for form association, allowing the component\nto participate in form submission, validation, and reset.\n\nNative input and change events bubble up from the inner input element.",
3468
+ "name": "KRTextField",
3467
3469
  "members": [
3468
3470
  {
3469
3471
  "kind": "field",
3470
- "name": "value",
3472
+ "name": "formAssociated",
3471
3473
  "type": {
3472
- "text": "string"
3474
+ "text": "boolean"
3473
3475
  },
3474
- "default": "''",
3475
- "description": "The option value",
3476
- "attribute": "value"
3476
+ "static": true,
3477
+ "default": "true"
3477
3478
  },
3478
3479
  {
3479
3480
  "kind": "field",
3480
- "name": "disabled",
3481
+ "name": "_internals",
3481
3482
  "type": {
3482
- "text": "boolean"
3483
+ "text": "ElementInternals"
3483
3484
  },
3484
- "default": "false",
3485
- "description": "Whether the option is disabled",
3486
- "attribute": "disabled"
3485
+ "privacy": "private"
3487
3486
  },
3488
3487
  {
3489
3488
  "kind": "field",
@@ -3491,128 +3490,59 @@
3491
3490
  "type": {
3492
3491
  "text": "string"
3493
3492
  },
3494
- "description": "Gets the label text from the slot",
3495
- "readonly": true
3496
- }
3497
- ],
3498
- "attributes": [
3493
+ "default": "''",
3494
+ "description": "The input label text",
3495
+ "attribute": "label"
3496
+ },
3499
3497
  {
3500
- "name": "value",
3498
+ "kind": "field",
3499
+ "name": "name",
3501
3500
  "type": {
3502
3501
  "text": "string"
3503
3502
  },
3504
3503
  "default": "''",
3505
- "description": "The option value",
3506
- "fieldName": "value"
3507
- },
3508
- {
3509
- "name": "disabled",
3510
- "type": {
3511
- "text": "boolean"
3512
- },
3513
- "default": "false",
3514
- "description": "Whether the option is disabled",
3515
- "fieldName": "disabled"
3516
- }
3517
- ],
3518
- "superclass": {
3519
- "name": "LitElement",
3520
- "package": "lit"
3521
- },
3522
- "tagName": "kr-select-option",
3523
- "customElement": true
3524
- }
3525
- ],
3526
- "exports": [
3527
- {
3528
- "kind": "js",
3529
- "name": "KRSelectOption",
3530
- "declaration": {
3531
- "name": "KRSelectOption",
3532
- "module": "src/form/select/select-option.ts"
3533
- }
3534
- },
3535
- {
3536
- "kind": "custom-element-definition",
3537
- "name": "kr-select-option",
3538
- "declaration": {
3539
- "name": "KRSelectOption",
3540
- "module": "src/form/select/select-option.ts"
3541
- }
3542
- }
3543
- ]
3544
- },
3545
- {
3546
- "kind": "javascript-module",
3547
- "path": "src/form/select/select.ts",
3548
- "declarations": [
3549
- {
3550
- "kind": "class",
3551
- "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.",
3552
- "name": "KRSelect",
3553
- "slots": [
3554
- {
3555
- "description": "The kr-select-option elements",
3556
- "name": ""
3557
- }
3558
- ],
3559
- "members": [
3560
- {
3561
- "kind": "field",
3562
- "name": "formAssociated",
3563
- "type": {
3564
- "text": "boolean"
3565
- },
3566
- "static": true,
3567
- "default": "true"
3568
- },
3569
- {
3570
- "kind": "field",
3571
- "name": "_internals",
3572
- "type": {
3573
- "text": "ElementInternals"
3574
- },
3575
- "privacy": "private"
3504
+ "description": "The input name for form submission",
3505
+ "attribute": "name"
3576
3506
  },
3577
3507
  {
3578
3508
  "kind": "field",
3579
- "name": "label",
3509
+ "name": "value",
3580
3510
  "type": {
3581
3511
  "text": "string"
3582
3512
  },
3583
3513
  "default": "''",
3584
- "description": "The select label text",
3585
- "attribute": "label"
3514
+ "description": "The current value",
3515
+ "attribute": "value"
3586
3516
  },
3587
3517
  {
3588
3518
  "kind": "field",
3589
- "name": "name",
3519
+ "name": "placeholder",
3590
3520
  "type": {
3591
3521
  "text": "string"
3592
3522
  },
3593
3523
  "default": "''",
3594
- "description": "The input name for form submission",
3595
- "attribute": "name"
3524
+ "description": "Placeholder text",
3525
+ "attribute": "placeholder"
3596
3526
  },
3597
3527
  {
3598
3528
  "kind": "field",
3599
- "name": "value",
3529
+ "name": "type",
3600
3530
  "type": {
3601
- "text": "string"
3531
+ "text": "'text' | 'email' | 'password' | 'tel' | 'url' | 'search'"
3602
3532
  },
3603
- "default": "''",
3604
- "description": "The currently selected value",
3605
- "attribute": "value"
3533
+ "default": "'text'",
3534
+ "description": "Input type (text, email, password, tel, url, search)",
3535
+ "attribute": "type"
3606
3536
  },
3607
3537
  {
3608
3538
  "kind": "field",
3609
- "name": "placeholder",
3539
+ "name": "required",
3610
3540
  "type": {
3611
- "text": "string"
3541
+ "text": "boolean"
3612
3542
  },
3613
- "default": "'Select an option'",
3614
- "description": "Placeholder text when no option is selected",
3615
- "attribute": "placeholder"
3543
+ "default": "false",
3544
+ "description": "Whether the field is required",
3545
+ "attribute": "required"
3616
3546
  },
3617
3547
  {
3618
3548
  "kind": "field",
@@ -3621,56 +3551,73 @@
3621
3551
  "text": "boolean"
3622
3552
  },
3623
3553
  "default": "false",
3624
- "description": "Whether the select is disabled",
3554
+ "description": "Whether the field is disabled",
3625
3555
  "attribute": "disabled"
3626
3556
  },
3627
3557
  {
3628
3558
  "kind": "field",
3629
- "name": "required",
3559
+ "name": "readonly",
3630
3560
  "type": {
3631
3561
  "text": "boolean"
3632
3562
  },
3633
3563
  "default": "false",
3634
- "description": "Whether the field is required",
3635
- "attribute": "required"
3564
+ "description": "Whether the field is readonly",
3565
+ "attribute": "readonly"
3636
3566
  },
3637
3567
  {
3638
3568
  "kind": "field",
3639
- "name": "readonly",
3569
+ "name": "minlength",
3640
3570
  "type": {
3641
- "text": "boolean"
3571
+ "text": "number | undefined"
3642
3572
  },
3643
- "default": "false",
3644
- "description": "Whether the field is readonly",
3645
- "attribute": "readonly"
3573
+ "description": "Minimum length for the value",
3574
+ "attribute": "minlength"
3646
3575
  },
3647
3576
  {
3648
3577
  "kind": "field",
3649
- "name": "hint",
3578
+ "name": "maxlength",
3579
+ "type": {
3580
+ "text": "number | undefined"
3581
+ },
3582
+ "description": "Maximum length for the value",
3583
+ "attribute": "maxlength"
3584
+ },
3585
+ {
3586
+ "kind": "field",
3587
+ "name": "pattern",
3588
+ "type": {
3589
+ "text": "string | undefined"
3590
+ },
3591
+ "description": "Pattern for validation (regex)",
3592
+ "attribute": "pattern"
3593
+ },
3594
+ {
3595
+ "kind": "field",
3596
+ "name": "autocomplete",
3650
3597
  "type": {
3651
3598
  "text": "string"
3652
3599
  },
3653
3600
  "default": "''",
3654
- "description": "Helper text shown below the select",
3655
- "attribute": "hint"
3601
+ "description": "Autocomplete attribute value",
3602
+ "attribute": "autocomplete"
3656
3603
  },
3657
3604
  {
3658
3605
  "kind": "field",
3659
- "name": "_isOpen",
3606
+ "name": "hint",
3660
3607
  "type": {
3661
- "text": "boolean"
3608
+ "text": "string"
3662
3609
  },
3663
- "privacy": "private",
3664
- "default": "false"
3610
+ "default": "''",
3611
+ "description": "Helper text shown below the input",
3612
+ "attribute": "hint"
3665
3613
  },
3666
3614
  {
3667
3615
  "kind": "field",
3668
- "name": "_highlightedIndex",
3616
+ "name": "_input",
3669
3617
  "type": {
3670
- "text": "number"
3618
+ "text": "HTMLInputElement"
3671
3619
  },
3672
- "privacy": "private",
3673
- "default": "-1"
3620
+ "privacy": "private"
3674
3621
  },
3675
3622
  {
3676
3623
  "kind": "field",
@@ -3683,11 +3630,12 @@
3683
3630
  },
3684
3631
  {
3685
3632
  "kind": "field",
3686
- "name": "_triggerElement",
3633
+ "name": "_dirty",
3687
3634
  "type": {
3688
- "text": "HTMLButtonElement"
3635
+ "text": "boolean"
3689
3636
  },
3690
- "privacy": "private"
3637
+ "privacy": "private",
3638
+ "default": "false"
3691
3639
  },
3692
3640
  {
3693
3641
  "kind": "field",
@@ -3738,35 +3686,33 @@
3738
3686
  "name": "_handleInvalid",
3739
3687
  "privacy": "private"
3740
3688
  },
3741
- {
3742
- "kind": "field",
3743
- "name": "_handleOutsideClick",
3744
- "privacy": "private"
3745
- },
3746
- {
3747
- "kind": "field",
3748
- "name": "_handleKeyDown",
3749
- "privacy": "private"
3750
- },
3751
3689
  {
3752
3690
  "kind": "method",
3753
- "name": "_toggle",
3691
+ "name": "_updateValidity",
3754
3692
  "privacy": "private"
3755
3693
  },
3756
3694
  {
3757
3695
  "kind": "method",
3758
- "name": "_close",
3759
- "privacy": "private"
3696
+ "name": "_handleInput",
3697
+ "privacy": "private",
3698
+ "parameters": [
3699
+ {
3700
+ "name": "e",
3701
+ "type": {
3702
+ "text": "Event"
3703
+ }
3704
+ }
3705
+ ]
3760
3706
  },
3761
3707
  {
3762
3708
  "kind": "method",
3763
- "name": "_selectOption",
3709
+ "name": "_handleChange",
3764
3710
  "privacy": "private",
3765
3711
  "parameters": [
3766
3712
  {
3767
- "name": "option",
3713
+ "name": "e",
3768
3714
  "type": {
3769
- "text": "KRSelectOption"
3715
+ "text": "Event"
3770
3716
  }
3771
3717
  }
3772
3718
  ]
@@ -3776,11 +3722,6 @@
3776
3722
  "name": "_handleBlur",
3777
3723
  "privacy": "private"
3778
3724
  },
3779
- {
3780
- "kind": "method",
3781
- "name": "_updateValidity",
3782
- "privacy": "private"
3783
- },
3784
3725
  {
3785
3726
  "kind": "method",
3786
3727
  "name": "focus"
@@ -3788,14 +3729,10 @@
3788
3729
  {
3789
3730
  "kind": "method",
3790
3731
  "name": "blur"
3791
- }
3792
- ],
3793
- "events": [
3732
+ },
3794
3733
  {
3795
- "name": "change",
3796
- "type": {
3797
- "text": "Event"
3798
- }
3734
+ "kind": "method",
3735
+ "name": "select"
3799
3736
  }
3800
3737
  ],
3801
3738
  "attributes": [
@@ -3805,7 +3742,7 @@
3805
3742
  "text": "string"
3806
3743
  },
3807
3744
  "default": "''",
3808
- "description": "The select label text",
3745
+ "description": "The input label text",
3809
3746
  "fieldName": "label"
3810
3747
  },
3811
3748
  {
@@ -3823,7 +3760,7 @@
3823
3760
  "text": "string"
3824
3761
  },
3825
3762
  "default": "''",
3826
- "description": "The currently selected value",
3763
+ "description": "The current value",
3827
3764
  "fieldName": "value"
3828
3765
  },
3829
3766
  {
@@ -3831,82 +3768,266 @@
3831
3768
  "type": {
3832
3769
  "text": "string"
3833
3770
  },
3834
- "default": "'Select an option'",
3835
- "description": "Placeholder text when no option is selected",
3771
+ "default": "''",
3772
+ "description": "Placeholder text",
3836
3773
  "fieldName": "placeholder"
3837
3774
  },
3775
+ {
3776
+ "name": "type",
3777
+ "type": {
3778
+ "text": "'text' | 'email' | 'password' | 'tel' | 'url' | 'search'"
3779
+ },
3780
+ "default": "'text'",
3781
+ "description": "Input type (text, email, password, tel, url, search)",
3782
+ "fieldName": "type"
3783
+ },
3784
+ {
3785
+ "name": "required",
3786
+ "type": {
3787
+ "text": "boolean"
3788
+ },
3789
+ "default": "false",
3790
+ "description": "Whether the field is required",
3791
+ "fieldName": "required"
3792
+ },
3838
3793
  {
3839
3794
  "name": "disabled",
3840
3795
  "type": {
3841
3796
  "text": "boolean"
3842
3797
  },
3843
3798
  "default": "false",
3844
- "description": "Whether the select is disabled",
3799
+ "description": "Whether the field is disabled",
3845
3800
  "fieldName": "disabled"
3846
3801
  },
3847
3802
  {
3848
- "name": "required",
3803
+ "name": "readonly",
3804
+ "type": {
3805
+ "text": "boolean"
3806
+ },
3807
+ "default": "false",
3808
+ "description": "Whether the field is readonly",
3809
+ "fieldName": "readonly"
3810
+ },
3811
+ {
3812
+ "name": "minlength",
3813
+ "type": {
3814
+ "text": "number | undefined"
3815
+ },
3816
+ "description": "Minimum length for the value",
3817
+ "fieldName": "minlength"
3818
+ },
3819
+ {
3820
+ "name": "maxlength",
3821
+ "type": {
3822
+ "text": "number | undefined"
3823
+ },
3824
+ "description": "Maximum length for the value",
3825
+ "fieldName": "maxlength"
3826
+ },
3827
+ {
3828
+ "name": "pattern",
3829
+ "type": {
3830
+ "text": "string | undefined"
3831
+ },
3832
+ "description": "Pattern for validation (regex)",
3833
+ "fieldName": "pattern"
3834
+ },
3835
+ {
3836
+ "name": "autocomplete",
3837
+ "type": {
3838
+ "text": "string"
3839
+ },
3840
+ "default": "''",
3841
+ "description": "Autocomplete attribute value",
3842
+ "fieldName": "autocomplete"
3843
+ },
3844
+ {
3845
+ "name": "hint",
3846
+ "type": {
3847
+ "text": "string"
3848
+ },
3849
+ "default": "''",
3850
+ "description": "Helper text shown below the input",
3851
+ "fieldName": "hint"
3852
+ }
3853
+ ],
3854
+ "superclass": {
3855
+ "name": "LitElement",
3856
+ "package": "lit"
3857
+ },
3858
+ "tagName": "kr-text-field",
3859
+ "customElement": true
3860
+ }
3861
+ ],
3862
+ "exports": [
3863
+ {
3864
+ "kind": "js",
3865
+ "name": "KRTextField",
3866
+ "declaration": {
3867
+ "name": "KRTextField",
3868
+ "module": "src/form/text-field/text-field.ts"
3869
+ }
3870
+ },
3871
+ {
3872
+ "kind": "custom-element-definition",
3873
+ "name": "kr-text-field",
3874
+ "declaration": {
3875
+ "name": "KRTextField",
3876
+ "module": "src/form/text-field/text-field.ts"
3877
+ }
3878
+ }
3879
+ ]
3880
+ },
3881
+ {
3882
+ "kind": "javascript-module",
3883
+ "path": "dist/form/select/select-option.js",
3884
+ "declarations": [
3885
+ {
3886
+ "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."
3890
+ }
3891
+ ],
3892
+ "exports": [
3893
+ {
3894
+ "kind": "js",
3895
+ "name": "KRSelectOption",
3896
+ "declaration": {
3897
+ "name": "KRSelectOption",
3898
+ "module": "dist/form/select/select-option.js"
3899
+ }
3900
+ }
3901
+ ]
3902
+ },
3903
+ {
3904
+ "kind": "javascript-module",
3905
+ "path": "dist/form/select/select.js",
3906
+ "declarations": [
3907
+ {
3908
+ "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(); } }",
3911
+ "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
+ }
3913
+ ],
3914
+ "exports": [
3915
+ {
3916
+ "kind": "js",
3917
+ "name": "KRSelect",
3918
+ "declaration": {
3919
+ "name": "KRSelect",
3920
+ "module": "dist/form/select/select.js"
3921
+ }
3922
+ }
3923
+ ]
3924
+ },
3925
+ {
3926
+ "kind": "javascript-module",
3927
+ "path": "src/form/select/select-option.ts",
3928
+ "declarations": [
3929
+ {
3930
+ "kind": "class",
3931
+ "description": "An option for the kr-select component.",
3932
+ "name": "KRSelectOption",
3933
+ "slots": [
3934
+ {
3935
+ "description": "The option label content",
3936
+ "name": ""
3937
+ }
3938
+ ],
3939
+ "members": [
3940
+ {
3941
+ "kind": "field",
3942
+ "name": "value",
3943
+ "type": {
3944
+ "text": "string"
3945
+ },
3946
+ "default": "''",
3947
+ "description": "The option value",
3948
+ "attribute": "value"
3949
+ },
3950
+ {
3951
+ "kind": "field",
3952
+ "name": "disabled",
3953
+ "type": {
3954
+ "text": "boolean"
3955
+ },
3956
+ "default": "false",
3957
+ "description": "Whether the option is disabled",
3958
+ "attribute": "disabled"
3959
+ },
3960
+ {
3961
+ "kind": "field",
3962
+ "name": "label",
3963
+ "type": {
3964
+ "text": "string"
3965
+ },
3966
+ "description": "Gets the label text from the slot",
3967
+ "readonly": true
3968
+ }
3969
+ ],
3970
+ "attributes": [
3971
+ {
3972
+ "name": "value",
3849
3973
  "type": {
3850
- "text": "boolean"
3974
+ "text": "string"
3851
3975
  },
3852
- "default": "false",
3853
- "description": "Whether the field is required",
3854
- "fieldName": "required"
3976
+ "default": "''",
3977
+ "description": "The option value",
3978
+ "fieldName": "value"
3855
3979
  },
3856
3980
  {
3857
- "name": "readonly",
3981
+ "name": "disabled",
3858
3982
  "type": {
3859
3983
  "text": "boolean"
3860
3984
  },
3861
3985
  "default": "false",
3862
- "description": "Whether the field is readonly",
3863
- "fieldName": "readonly"
3864
- },
3865
- {
3866
- "name": "hint",
3867
- "type": {
3868
- "text": "string"
3869
- },
3870
- "default": "''",
3871
- "description": "Helper text shown below the select",
3872
- "fieldName": "hint"
3986
+ "description": "Whether the option is disabled",
3987
+ "fieldName": "disabled"
3873
3988
  }
3874
3989
  ],
3875
3990
  "superclass": {
3876
3991
  "name": "LitElement",
3877
3992
  "package": "lit"
3878
3993
  },
3879
- "tagName": "kr-select",
3994
+ "tagName": "kr-select-option",
3880
3995
  "customElement": true
3881
3996
  }
3882
3997
  ],
3883
3998
  "exports": [
3884
3999
  {
3885
4000
  "kind": "js",
3886
- "name": "KRSelect",
4001
+ "name": "KRSelectOption",
3887
4002
  "declaration": {
3888
- "name": "KRSelect",
3889
- "module": "src/form/select/select.ts"
4003
+ "name": "KRSelectOption",
4004
+ "module": "src/form/select/select-option.ts"
3890
4005
  }
3891
4006
  },
3892
4007
  {
3893
4008
  "kind": "custom-element-definition",
3894
- "name": "kr-select",
4009
+ "name": "kr-select-option",
3895
4010
  "declaration": {
3896
- "name": "KRSelect",
3897
- "module": "src/form/select/select.ts"
4011
+ "name": "KRSelectOption",
4012
+ "module": "src/form/select/select-option.ts"
3898
4013
  }
3899
4014
  }
3900
4015
  ]
3901
4016
  },
3902
4017
  {
3903
4018
  "kind": "javascript-module",
3904
- "path": "src/form/text-field/text-field.ts",
4019
+ "path": "src/form/select/select.ts",
3905
4020
  "declarations": [
3906
4021
  {
3907
4022
  "kind": "class",
3908
- "description": "A text field component that works with native browser forms.\n\nUses ElementInternals for form association, allowing the component\nto participate in form submission, validation, and reset.\n\nNative input and change events bubble up from the inner input element.",
3909
- "name": "KRTextField",
4023
+ "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",
4025
+ "slots": [
4026
+ {
4027
+ "description": "The kr-select-option elements",
4028
+ "name": ""
4029
+ }
4030
+ ],
3910
4031
  "members": [
3911
4032
  {
3912
4033
  "kind": "field",
@@ -3932,7 +4053,7 @@
3932
4053
  "text": "string"
3933
4054
  },
3934
4055
  "default": "''",
3935
- "description": "The input label text",
4056
+ "description": "The select label text",
3936
4057
  "attribute": "label"
3937
4058
  },
3938
4059
  {
@@ -3952,7 +4073,7 @@
3952
4073
  "text": "string"
3953
4074
  },
3954
4075
  "default": "''",
3955
- "description": "The current value",
4076
+ "description": "The currently selected value",
3956
4077
  "attribute": "value"
3957
4078
  },
3958
4079
  {
@@ -3961,19 +4082,19 @@
3961
4082
  "type": {
3962
4083
  "text": "string"
3963
4084
  },
3964
- "default": "''",
3965
- "description": "Placeholder text",
4085
+ "default": "'Select an option'",
4086
+ "description": "Placeholder text when no option is selected",
3966
4087
  "attribute": "placeholder"
3967
4088
  },
3968
4089
  {
3969
4090
  "kind": "field",
3970
- "name": "type",
4091
+ "name": "disabled",
3971
4092
  "type": {
3972
- "text": "'text' | 'email' | 'password' | 'tel' | 'url' | 'search'"
4093
+ "text": "boolean"
3973
4094
  },
3974
- "default": "'text'",
3975
- "description": "Input type (text, email, password, tel, url, search)",
3976
- "attribute": "type"
4095
+ "default": "false",
4096
+ "description": "Whether the select is disabled",
4097
+ "attribute": "disabled"
3977
4098
  },
3978
4099
  {
3979
4100
  "kind": "field",
@@ -3985,16 +4106,6 @@
3985
4106
  "description": "Whether the field is required",
3986
4107
  "attribute": "required"
3987
4108
  },
3988
- {
3989
- "kind": "field",
3990
- "name": "disabled",
3991
- "type": {
3992
- "text": "boolean"
3993
- },
3994
- "default": "false",
3995
- "description": "Whether the field is disabled",
3996
- "attribute": "disabled"
3997
- },
3998
4109
  {
3999
4110
  "kind": "field",
4000
4111
  "name": "readonly",
@@ -4007,58 +4118,31 @@
4007
4118
  },
4008
4119
  {
4009
4120
  "kind": "field",
4010
- "name": "minlength",
4011
- "type": {
4012
- "text": "number | undefined"
4013
- },
4014
- "description": "Minimum length for the value",
4015
- "attribute": "minlength"
4016
- },
4017
- {
4018
- "kind": "field",
4019
- "name": "maxlength",
4020
- "type": {
4021
- "text": "number | undefined"
4022
- },
4023
- "description": "Maximum length for the value",
4024
- "attribute": "maxlength"
4025
- },
4026
- {
4027
- "kind": "field",
4028
- "name": "pattern",
4029
- "type": {
4030
- "text": "string | undefined"
4031
- },
4032
- "description": "Pattern for validation (regex)",
4033
- "attribute": "pattern"
4034
- },
4035
- {
4036
- "kind": "field",
4037
- "name": "autocomplete",
4121
+ "name": "hint",
4038
4122
  "type": {
4039
4123
  "text": "string"
4040
4124
  },
4041
4125
  "default": "''",
4042
- "description": "Autocomplete attribute value",
4043
- "attribute": "autocomplete"
4126
+ "description": "Helper text shown below the select",
4127
+ "attribute": "hint"
4044
4128
  },
4045
4129
  {
4046
4130
  "kind": "field",
4047
- "name": "hint",
4131
+ "name": "_isOpen",
4048
4132
  "type": {
4049
- "text": "string"
4133
+ "text": "boolean"
4050
4134
  },
4051
- "default": "''",
4052
- "description": "Helper text shown below the input",
4053
- "attribute": "hint"
4135
+ "privacy": "private",
4136
+ "default": "false"
4054
4137
  },
4055
4138
  {
4056
4139
  "kind": "field",
4057
- "name": "_input",
4140
+ "name": "_highlightedIndex",
4058
4141
  "type": {
4059
- "text": "HTMLInputElement"
4142
+ "text": "number"
4060
4143
  },
4061
- "privacy": "private"
4144
+ "privacy": "private",
4145
+ "default": "-1"
4062
4146
  },
4063
4147
  {
4064
4148
  "kind": "field",
@@ -4071,12 +4155,11 @@
4071
4155
  },
4072
4156
  {
4073
4157
  "kind": "field",
4074
- "name": "_dirty",
4158
+ "name": "_triggerElement",
4075
4159
  "type": {
4076
- "text": "boolean"
4160
+ "text": "HTMLButtonElement"
4077
4161
  },
4078
- "privacy": "private",
4079
- "default": "false"
4162
+ "privacy": "private"
4080
4163
  },
4081
4164
  {
4082
4165
  "kind": "field",
@@ -4127,33 +4210,35 @@
4127
4210
  "name": "_handleInvalid",
4128
4211
  "privacy": "private"
4129
4212
  },
4213
+ {
4214
+ "kind": "field",
4215
+ "name": "_handleOutsideClick",
4216
+ "privacy": "private"
4217
+ },
4218
+ {
4219
+ "kind": "field",
4220
+ "name": "_handleKeyDown",
4221
+ "privacy": "private"
4222
+ },
4130
4223
  {
4131
4224
  "kind": "method",
4132
- "name": "_updateValidity",
4225
+ "name": "_toggle",
4133
4226
  "privacy": "private"
4134
4227
  },
4135
4228
  {
4136
4229
  "kind": "method",
4137
- "name": "_handleInput",
4138
- "privacy": "private",
4139
- "parameters": [
4140
- {
4141
- "name": "e",
4142
- "type": {
4143
- "text": "Event"
4144
- }
4145
- }
4146
- ]
4230
+ "name": "_close",
4231
+ "privacy": "private"
4147
4232
  },
4148
4233
  {
4149
4234
  "kind": "method",
4150
- "name": "_handleChange",
4235
+ "name": "_selectOption",
4151
4236
  "privacy": "private",
4152
4237
  "parameters": [
4153
4238
  {
4154
- "name": "e",
4239
+ "name": "option",
4155
4240
  "type": {
4156
- "text": "Event"
4241
+ "text": "KRSelectOption"
4157
4242
  }
4158
4243
  }
4159
4244
  ]
@@ -4165,15 +4250,24 @@
4165
4250
  },
4166
4251
  {
4167
4252
  "kind": "method",
4168
- "name": "focus"
4253
+ "name": "_updateValidity",
4254
+ "privacy": "private"
4169
4255
  },
4170
4256
  {
4171
4257
  "kind": "method",
4172
- "name": "blur"
4258
+ "name": "focus"
4173
4259
  },
4174
4260
  {
4175
4261
  "kind": "method",
4176
- "name": "select"
4262
+ "name": "blur"
4263
+ }
4264
+ ],
4265
+ "events": [
4266
+ {
4267
+ "name": "change",
4268
+ "type": {
4269
+ "text": "Event"
4270
+ }
4177
4271
  }
4178
4272
  ],
4179
4273
  "attributes": [
@@ -4183,7 +4277,7 @@
4183
4277
  "text": "string"
4184
4278
  },
4185
4279
  "default": "''",
4186
- "description": "The input label text",
4280
+ "description": "The select label text",
4187
4281
  "fieldName": "label"
4188
4282
  },
4189
4283
  {
@@ -4201,7 +4295,7 @@
4201
4295
  "text": "string"
4202
4296
  },
4203
4297
  "default": "''",
4204
- "description": "The current value",
4298
+ "description": "The currently selected value",
4205
4299
  "fieldName": "value"
4206
4300
  },
4207
4301
  {
@@ -4209,18 +4303,18 @@
4209
4303
  "type": {
4210
4304
  "text": "string"
4211
4305
  },
4212
- "default": "''",
4213
- "description": "Placeholder text",
4306
+ "default": "'Select an option'",
4307
+ "description": "Placeholder text when no option is selected",
4214
4308
  "fieldName": "placeholder"
4215
4309
  },
4216
4310
  {
4217
- "name": "type",
4311
+ "name": "disabled",
4218
4312
  "type": {
4219
- "text": "'text' | 'email' | 'password' | 'tel' | 'url' | 'search'"
4313
+ "text": "boolean"
4220
4314
  },
4221
- "default": "'text'",
4222
- "description": "Input type (text, email, password, tel, url, search)",
4223
- "fieldName": "type"
4315
+ "default": "false",
4316
+ "description": "Whether the select is disabled",
4317
+ "fieldName": "disabled"
4224
4318
  },
4225
4319
  {
4226
4320
  "name": "required",
@@ -4231,15 +4325,6 @@
4231
4325
  "description": "Whether the field is required",
4232
4326
  "fieldName": "required"
4233
4327
  },
4234
- {
4235
- "name": "disabled",
4236
- "type": {
4237
- "text": "boolean"
4238
- },
4239
- "default": "false",
4240
- "description": "Whether the field is disabled",
4241
- "fieldName": "disabled"
4242
- },
4243
4328
  {
4244
4329
  "name": "readonly",
4245
4330
  "type": {
@@ -4249,46 +4334,13 @@
4249
4334
  "description": "Whether the field is readonly",
4250
4335
  "fieldName": "readonly"
4251
4336
  },
4252
- {
4253
- "name": "minlength",
4254
- "type": {
4255
- "text": "number | undefined"
4256
- },
4257
- "description": "Minimum length for the value",
4258
- "fieldName": "minlength"
4259
- },
4260
- {
4261
- "name": "maxlength",
4262
- "type": {
4263
- "text": "number | undefined"
4264
- },
4265
- "description": "Maximum length for the value",
4266
- "fieldName": "maxlength"
4267
- },
4268
- {
4269
- "name": "pattern",
4270
- "type": {
4271
- "text": "string | undefined"
4272
- },
4273
- "description": "Pattern for validation (regex)",
4274
- "fieldName": "pattern"
4275
- },
4276
- {
4277
- "name": "autocomplete",
4278
- "type": {
4279
- "text": "string"
4280
- },
4281
- "default": "''",
4282
- "description": "Autocomplete attribute value",
4283
- "fieldName": "autocomplete"
4284
- },
4285
4337
  {
4286
4338
  "name": "hint",
4287
4339
  "type": {
4288
4340
  "text": "string"
4289
4341
  },
4290
4342
  "default": "''",
4291
- "description": "Helper text shown below the input",
4343
+ "description": "Helper text shown below the select",
4292
4344
  "fieldName": "hint"
4293
4345
  }
4294
4346
  ],
@@ -4296,25 +4348,25 @@
4296
4348
  "name": "LitElement",
4297
4349
  "package": "lit"
4298
4350
  },
4299
- "tagName": "kr-text-field",
4351
+ "tagName": "kr-select",
4300
4352
  "customElement": true
4301
4353
  }
4302
4354
  ],
4303
4355
  "exports": [
4304
4356
  {
4305
4357
  "kind": "js",
4306
- "name": "KRTextField",
4358
+ "name": "KRSelect",
4307
4359
  "declaration": {
4308
- "name": "KRTextField",
4309
- "module": "src/form/text-field/text-field.ts"
4360
+ "name": "KRSelect",
4361
+ "module": "src/form/select/select.ts"
4310
4362
  }
4311
4363
  },
4312
4364
  {
4313
4365
  "kind": "custom-element-definition",
4314
- "name": "kr-text-field",
4366
+ "name": "kr-select",
4315
4367
  "declaration": {
4316
- "name": "KRTextField",
4317
- "module": "src/form/text-field/text-field.ts"
4368
+ "name": "KRSelect",
4369
+ "module": "src/form/select/select.ts"
4318
4370
  }
4319
4371
  }
4320
4372
  ]