vanillajs-excelike-table
Version:
A user-friendly pure JavaScript table library with Excel-like features, preset configurations, and intuitive column helpers. Vanilla JS implementation - no frameworks required!
1 lines • 66 kB
JavaScript
const TABLE_PRESETS={simple:{features:["sorting"],bordered:!0,size:"middle",pagination:{pageSize:10}},standard:{features:["sorting","filtering","pagination"],bordered:!0,size:"middle",pagination:{pageSize:10,showSizeChanger:!0}},advanced:{features:["sorting","filtering","pagination","columnSettings","persistSettings"],bordered:!0,size:"middle",pagination:{pageSize:10,showSizeChanger:!0}},excel:{features:["sorting","filtering","pagination","columnSettings","persistSettings","columnResizing","columnPinning"],bordered:!0,size:"middle",pagination:{pageSize:10,showSizeChanger:!0}}},TABLE_CONFIG={FONT_SIZES:{smallest:"10px",small:"12px",medium:"14px",large:"18px",largest:"22px"},CELL_PADDING:{wide:{vertical:"12px",horizontal:"8px"},standard:{vertical:"8px",horizontal:"6px"},narrow:{vertical:"2px",horizontal:"2px"}},SIZES:{MIN_COLUMN_WIDTH:50,AUTO_RESIZE_MIN:80,AUTO_RESIZE_MAX:400,DOUBLE_CLICK_THRESHOLD:300,TOOLTIP_OFFSET:{x:15,y:40}},Z_INDEXES:{TOOLTIP:1e4,FILTER_DROPDOWN:9999,PINNED_HEADER:101,PINNED_CELL:100},TIMING:{TOOLTIP_FADE:200,DEBOUNCE_RESIZE:100}},ColumnHelpers={text:(e,t,a={})=>({key:e,title:t,dataIndex:e,width:a.width||150,sortable:!1!==a.sortable,filterable:!1!==a.filterable,...a}),number:(e,t,a={})=>({key:e,title:t,dataIndex:e,width:a.width||120,sortable:!1!==a.sortable,filterable:!1!==a.filterable,render:a.render||(e=>"number"==typeof e?a.currency?`${a.currency}${e.toLocaleString()}`:e.toLocaleString():e),...a}),date:(e,t,a={})=>({key:e,title:t,dataIndex:e,type:"date",width:a.width||130,sortable:!1!==a.sortable,filterable:!1!==a.filterable,filterType:"date-hierarchy",render:a.render||(e=>e?new Date(e).toLocaleDateString():""),...a}),status:(e,t,a={},n={})=>({key:e,title:t,dataIndex:e,width:n.width||100,sortable:!1!==n.sortable,filterable:!1!==n.filterable,render:e=>`<span style="color: ${a[e]||"#000"}; font-weight: 500;">${e}</span>`,...n}),actions:(e,t,a={})=>({key:"actions",title:e||"Actions",dataIndex:"actions",width:a.width||120,sortable:!1,filterable:!1,render:(e,a)=>t.map(e=>`<button class="action-btn" data-action="${e.key}" data-id="${a.id||a.key}">${e.label}</button>`).join(" "),...a})},TableUtils={getDecimalPlaces(e){let t=0;return e.forEach(e=>{if("number"==typeof e&&!Number.isInteger(e)){const a=e.toString().split(".")[1]?.length||0;t=Math.max(t,a)}}),t},formatNumberValue:(e,t)=>"number"!=typeof e?e:t>0?e.toFixed(t):Math.round(e).toString(),isNumericColumn:(e,t)=>t.slice(0,10).every(t=>{const a=t[e.dataIndex];return null==a||""===a||!isNaN(Number(a))}),isDateValue(e){if(!e)return!1;const t=new Date(e);return t instanceof Date&&!isNaN(t)&&e.toString().match(/^\d{4}-\d{2}-\d{2}/)},getCheckedFilterValues:(e,t=!0)=>Array.from(e.querySelectorAll(".filter-checkbox:checked")).map(e=>e.dataset.value).filter(e=>!t||"__all__"!==e),updateSelectAllState(e,t){const a=Array.from(t).filter(e=>e.checked).length,n=t.length;0===a?(e.checked=!1,e.indeterminate=!1):a===n?(e.checked=!0,e.indeterminate=!1):(e.checked=!1,e.indeterminate=!0)},pxToCm:e=>(2.54*e/(96*window.devicePixelRatio)).toFixed(2),constrainToViewport(e,t,a,n=0,i=0){const s=e.getBoundingClientRect(),l=window.innerWidth-s.width-10,r=window.innerHeight-s.height-10;return{x:Math.max(10,Math.min(t+n,l)),y:Math.max(10,Math.min(a+i,r))}}};class TableStorageAdapter{constructor(){this.storage=localStorage}async save(e,t){try{return this.storage.setItem(e,JSON.stringify(t)),!0}catch(e){return!1}}async load(e){try{const t=this.storage.getItem(e);return t?JSON.parse(t):null}catch(e){return null}}async remove(e){try{return this.storage.removeItem(e),!0}catch(e){return!1}}isAvailable(){try{const e="__table_storage_test__";return this.storage.setItem(e,"test"),this.storage.removeItem(e),!0}catch{return!1}}}class TableSettingsManager{constructor(e,t=null){this.tableId=e,this.storageAdapter=t||new TableStorageAdapter,this.storageKey=`excelike_table_${e}`}extractSettings(e){const t={};for(const[a,n]of Object.entries(e.state.columnWidths||{})){const e=Math.max(50,Math.min(2e3,n));t[a]=e}return{columnWidths:t,visibleColumns:{...e.state.visibleColumns},pinnedColumns:{...e.state.pinnedColumns},fontSize:e.state.fontSize,cellPadding:e.state.cellPadding,pageSize:e.state.pageSize,version:"1.0.0",timestamp:Date.now()}}applySettings(e,t){if(!t||!this.isValidSettings(t))return!1;try{return t.columnWidths&&Object.assign(e.state.columnWidths,t.columnWidths),t.visibleColumns&&Object.assign(e.state.visibleColumns,t.visibleColumns),t.pinnedColumns&&Object.assign(e.state.pinnedColumns,t.pinnedColumns),t.fontSize&&(e.state.fontSize=t.fontSize),t.cellPadding&&(e.state.cellPadding=t.cellPadding),t.pageSize&&(e.state.pageSize=t.pageSize),!0}catch(e){return!1}}isValidSettings(e){if(!e||"object"!=typeof e||!e.version||!e.timestamp)return!1;if(e.columnWidths)for(const[t,a]of Object.entries(e.columnWidths))if("number"!=typeof a||a<50||a>2e3)return!1;return!0}async saveSettings(e){if(!this.storageAdapter.isAvailable())return!1;const t=this.extractSettings(e);return await this.storageAdapter.save(this.storageKey,t)}async loadSettings(e){if(!this.storageAdapter.isAvailable())return!1;const t=await this.storageAdapter.load(this.storageKey);return!!t&&(this.isValidSettings(t)?this.applySettings(e,t):(await this.storageAdapter.remove(this.storageKey),!1))}async clearSettings(){return await this.storageAdapter.remove(this.storageKey)}}class ExceLikeTable{constructor(e,t={}){if(!e)throw new Error("Container element is required");if(this.container="string"==typeof e?document.querySelector(e):e,!this.container)throw new Error("Container element not found");let a={};t.preset&&TABLE_PRESETS[t.preset]&&(a={...TABLE_PRESETS[t.preset]}),this.options={data:[],columns:[],rowKey:"id",pagination:{pageSize:10,showSizeChanger:!0,showTotal:null,current:1},bordered:!0,size:"middle",loading:!1,tableId:"default",persistSettings:!1,storageAdapter:null,features:["sorting","filtering","pagination","columnSettings","persistSettings","columnResizing","columnPinning"],...a,...t},this.enabledFeatures=new Set(this.options.features),this.settingsManager=null,this.options.persistSettings&&(this.settingsManager=new TableSettingsManager(this.options.tableId,this.options.storageAdapter)),this.state={data:[...this.options.data],filteredData:[...this.options.data],filters:{},sortState:{column:null,direction:null},currentPage:this.options.pagination.current||1,pageSize:this.options.pagination.pageSize||10,columnWidths:{},visibleColumns:{},pinnedColumns:{},openFilter:null,rangeFilters:{},dateRangeFilters:{},fontSize:"medium",cellPadding:"standard"},this.initializeColumns(),this.handleSort=this.handleSort.bind(this),this.handleFilter=this.handleFilter.bind(this),this.handlePageChange=this.handlePageChange.bind(this),this.handleColumnResize=this.handleColumnResize.bind(this),this.handleColumnToggle=this.handleColumnToggle.bind(this),this.handleColumnPin=this.handleColumnPin.bind(this),this.init()}isFeatureEnabled(e){return this.enabledFeatures.has(e)}initializeColumns(){this.options.columns.forEach(e=>{this.state.columnWidths[e.key]=e.width||150,this.state.visibleColumns[e.key]=!0,this.state.pinnedColumns[e.key]=!1})}checkFixedHeight(){const e=getComputedStyle(this.container);e.height&&"auto"!==e.height&&"0px"!==e.height&&setTimeout(()=>{const e=this.tableContainer.querySelector(".table-container");e&&e.classList.add("fixed-height")},0)}async init(){this.container.innerHTML="",this.container.className="excelike-table-wrapper",this.createStructure(),this.settingsManager&&await this.loadSettings(),this.render(),this.attachEvents()}createStructure(){this.tableContainer=document.createElement("div"),this.tableContainer.className="excelike-table",this.checkFixedHeight(),this.loadingOverlay=document.createElement("div"),this.loadingOverlay.className="table-loading",this.loadingOverlay.style.display=this.options.loading?"flex":"none",this.loadingOverlay.innerHTML="<div>Loading...</div>",this.menuContainer=document.createElement("div"),this.menuContainer.className="table-menu-container",this.tableWrapper=document.createElement("div"),this.tableWrapper.className="table-container",this.table=document.createElement("table"),this.table.className=`table ${this.options.bordered?"bordered":""} ${this.options.size}`,this.thead=document.createElement("thead"),this.tbody=document.createElement("tbody"),this.table.appendChild(this.thead),this.table.appendChild(this.tbody),this.tableWrapper.appendChild(this.table),this.paginationContainer=document.createElement("div"),this.paginationContainer.className="enhanced-table-pagination",this.tableContainer.appendChild(this.loadingOverlay),this.tableContainer.appendChild(this.menuContainer),this.tableContainer.appendChild(this.tableWrapper),this.tableContainer.appendChild(this.paginationContainer),this.container.appendChild(this.tableContainer)}render(){this.applyFilters(),this.renderMenu(),this.renderHeader(),this.renderBody(),this.renderPagination(),this.applyCurrentStyling()}renderMenu(){if(!this.isFeatureEnabled("columnSettings"))return void(this.menuContainer.innerHTML="");let e=[];this.isFeatureEnabled("filtering")&&e.push('<div class="table-menu-item" data-action="clear-all-filters">フィルタ全解除</div>'),e.push('<div class="table-menu-item" data-action="column-settings">表示列設定</div>'),e.push(`\n <div class="table-menu-item table-menu-item-submenu" data-action="font-size">\n 文字サイズ\n <span class="submenu-arrow">▶</span>\n <div class="table-submenu-dropdown">\n <div class="table-menu-item ${"smallest"===this.state.fontSize?"table-menu-item-current":""}" data-action="font-size" data-size="smallest">最小 ${"smallest"===this.state.fontSize?"✓":""}</div>\n <div class="table-menu-item ${"small"===this.state.fontSize?"table-menu-item-current":""}" data-action="font-size" data-size="small">小 ${"small"===this.state.fontSize?"✓":""}</div>\n <div class="table-menu-item ${"medium"===this.state.fontSize?"table-menu-item-current":""}" data-action="font-size" data-size="medium">中 ${"medium"===this.state.fontSize?"✓":""}</div>\n <div class="table-menu-item ${"large"===this.state.fontSize?"table-menu-item-current":""}" data-action="font-size" data-size="large">大 ${"large"===this.state.fontSize?"✓":""}</div>\n <div class="table-menu-item ${"largest"===this.state.fontSize?"table-menu-item-current":""}" data-action="font-size" data-size="largest">最大 ${"largest"===this.state.fontSize?"✓":""}</div>\n </div>\n </div>\n `),e.push(`\n <div class="table-menu-item table-menu-item-submenu" data-action="cell-padding">\n セル内パディング\n <span class="submenu-arrow">▶</span>\n <div class="table-submenu-dropdown">\n <div class="table-menu-item ${"wide"===this.state.cellPadding?"table-menu-item-current":""}" data-action="cell-padding" data-padding="wide">広め ${"wide"===this.state.cellPadding?"✓":""}</div>\n <div class="table-menu-item ${"standard"===this.state.cellPadding?"table-menu-item-current":""}" data-action="cell-padding" data-padding="standard">標準 ${"standard"===this.state.cellPadding?"✓":""}</div>\n <div class="table-menu-item ${"narrow"===this.state.cellPadding?"table-menu-item-current":""}" data-action="cell-padding" data-padding="narrow">狭め ${"narrow"===this.state.cellPadding?"✓":""}</div>\n </div>\n </div>\n `),this.options.persistSettings&&e.push('<div class="table-menu-item" data-action="clear-localstorage">LocalStorageクリア</div>'),this.menuContainer.innerHTML=`\n <div class="table-menu-wrapper">\n <button class="table-menu-btn" data-menu-toggle>⚙️</button>\n <div class="table-menu-dropdown" style="display: none;">\n ${e.join("")}\n </div>\n </div>\n `}renderHeader(){const e=document.createElement("tr"),t=this.getVisibleColumns(),a=t.filter(e=>this.state.pinnedColumns[e.key]),n=t.filter(e=>!this.state.pinnedColumns[e.key]),i=[...a,...n];let s=0;i.forEach((t,n)=>{const i=document.createElement("th");i.className="table-header",i.style.width=`${this.state.columnWidths[t.key]}px`,this.state.pinnedColumns[t.key]&&(i.classList.add("pinned-column"),i.style.position="sticky",i.style.left=`${s}px`,i.style.zIndex="101",n===a.length-1&&i.classList.add("last-pinned-column"),s+=this.state.columnWidths[t.key]);const l=document.createElement("div");l.className="header-content";const r=document.createElement("span");r.className="header-title",r.textContent=t.title,this.addTooltipToCell(r,t.title);const o=document.createElement("div");if(o.className="header-controls",t.sortable&&this.isFeatureEnabled("sorting")){const e=this.createSortControls(t);o.appendChild(e)}if(t.filterable&&this.isFeatureEnabled("filtering")){const e=this.createFilterButton(t);o.appendChild(e)}if(l.appendChild(r),l.appendChild(o),this.isFeatureEnabled("columnResizing")){const e=document.createElement("div");e.className="resize-handle",e.dataset.column=t.key,l.appendChild(e)}i.appendChild(l),e.appendChild(i)}),this.thead.innerHTML="",this.thead.appendChild(e)}createSortControls(e){const t=document.createElement("div");t.className="sort-controls";const a=document.createElement("div");a.className="sort-indicator",a.dataset.column=e.key;const n=document.createElement("span");n.className="sort-triangle up "+(this.state.sortState.column===e.key&&"asc"===this.state.sortState.direction?"active":"");const i=document.createElement("span");return i.className="sort-triangle down "+(this.state.sortState.column===e.key&&"desc"===this.state.sortState.direction?"active":""),a.appendChild(n),a.appendChild(i),t.appendChild(a),t}createFilterButton(e){const t=document.createElement("div");t.className="filter-dropdown-container";const a=document.createElement("button");return a.className="filter-btn "+(this.hasActiveFilter(e.key)?"active":""),a.dataset.column=e.key,a.innerHTML='<span class="filter-funnel"></span>',t.appendChild(a),t}renderBody(){this.tbody.innerHTML="";const e=this.getVisibleColumns(),t=e.filter(e=>this.state.pinnedColumns[e.key]),a=e.filter(e=>!this.state.pinnedColumns[e.key]),n=[...t,...a];this.getPaginatedData().forEach(e=>{const a=document.createElement("tr");let i=0;n.forEach((n,s)=>{const l=document.createElement("td");l.className="table-cell",l.style.width=`${this.state.columnWidths[n.key]}px`,this.state.pinnedColumns[n.key]&&(l.classList.add("pinned-column"),l.style.position="sticky",l.style.left=`${i}px`,l.style.zIndex="100",s===t.length-1&&l.classList.add("last-pinned-column"),i+=this.state.columnWidths[n.key]);const r=e[n.dataIndex],o=n.render?n.render(r,e):r;"string"==typeof o?l.textContent=o:l.innerHTML=o,this.addTooltipToCell(l,o),a.appendChild(l)}),this.tbody.appendChild(a)})}renderPagination(){if(!this.isFeatureEnabled("pagination")||!1===this.options.pagination)return void(this.paginationContainer.innerHTML="");const e=this.state.filteredData.length,t=Math.ceil(e/this.state.pageSize),a=(this.state.currentPage-1)*this.state.pageSize+1,n=Math.min(this.state.currentPage*this.state.pageSize,e);let i="";this.options.pagination.showTotal?i+=`<div class="pagination-info">${this.options.pagination.showTotal(e,[a,n])}</div>`:i+=`<div class="pagination-info">${a}-${n} of ${e} items</div>`,i+=`\n <div class="pagination-controls">\n <button ${this.state.currentPage<=1?"disabled":""} data-page="${this.state.currentPage-1}">Previous</button>\n <span class="page-info">Page ${this.state.currentPage} of ${t}</span>\n <button ${this.state.currentPage>=t?"disabled":""} data-page="${this.state.currentPage+1}">Next</button>\n `,this.options.pagination.showSizeChanger&&(i+=`\n <div class="page-size-selector">\n <select class="page-size-select">\n <option value="5" ${5===this.state.pageSize?"selected":""}>5 / page</option>\n <option value="10" ${10===this.state.pageSize?"selected":""}>10 / page</option>\n <option value="20" ${20===this.state.pageSize?"selected":""}>20 / page</option>\n <option value="50" ${50===this.state.pageSize?"selected":""}>50 / page</option>\n </select>\n </div>\n `),i+="</div>",this.paginationContainer.innerHTML=i}getVisibleColumns(){return this.options.columns.filter(e=>this.state.visibleColumns[e.key])}getPaginatedData(){const e=(this.state.currentPage-1)*this.state.pageSize,t=e+this.state.pageSize;return this.state.filteredData.slice(e,t)}hasActiveFilter(e){return this.state.filters[e]&&this.state.filters[e].length>0||this.state.rangeFilters[e]&&null!==this.state.rangeFilters[e]}updateFilterButtonState(e){const t=this.tableContainer.querySelector(`.filter-btn[data-column="${e}"]`);if(t){const e=this.options.data.length;this.state.filteredData.length<e?t.classList.add("active"):t.classList.remove("active")}}handleSort(e,t){this.state.sortState=null===t?{column:null,direction:null}:{column:e,direction:t},this.applySorting(),this.render(),this.autoSaveSettings()}handleFilter(e,t){this.state.filters[e]=t,this.state.currentPage=1,this.render(),this.autoSaveSettings()}handlePageChange(e){this.state.currentPage=e,this.renderBody(),this.renderPagination()}handleColumnResize(e,t){this.state.columnWidths[e]=t,this.renderHeader(),this.renderBody(),this.applyCurrentStyling(),this.autoSaveSettings()}handleColumnToggle(e,t){this.state.visibleColumns[e]=t,this.render(),this.autoSaveSettings()}handleColumnPin(e,t){this.state.pinnedColumns[e]=t,this.render(),this.autoSaveSettings()}applyFilters(){let e=[...this.state.data];Object.keys(this.state.filters).forEach(t=>{const a=this.state.filters[t];if(a&&a.length>0){const n=this.options.columns.find(e=>e.key===t);n&&(e="date-hierarchy"===n.filterType?e.filter(e=>{const t=e[n.dataIndex];if(!t)return!1;const i=new Date(t),s=i.getFullYear().toString(),l=i.toLocaleString("default",{month:"long"});return a.some(e=>e===s||e===`${s}-${l}`)}):n.onFilter?e.filter(e=>a.some(t=>n.onFilter(t,e))):this.isNumericColumn(n)?e.filter(e=>{const t=parseFloat(e[n.dataIndex]);return!isNaN(t)&&a.includes(t)}):e.filter(e=>a.includes(e[n.dataIndex])))}}),Object.keys(this.state.rangeFilters).forEach(t=>{const a=this.state.rangeFilters[t];if(a){const n=this.options.columns.find(e=>e.key===t);n&&(e=e.filter(e=>{const t=parseFloat(e[n.dataIndex]);return!isNaN(t)&&t>=a.min&&t<=a.max}))}}),this.state.filteredData=e,this.applySorting()}applySorting(){if(this.state.sortState.column&&this.state.sortState.direction){const e=this.options.columns.find(e=>e.key===this.state.sortState.column);e&&this.state.filteredData.sort((t,a)=>{if(e.sorter)return"asc"===this.state.sortState.direction?e.sorter(t,a):e.sorter(a,t);{const n=t[e.dataIndex],i=a[e.dataIndex];return n<i?"asc"===this.state.sortState.direction?-1:1:n>i?"asc"===this.state.sortState.direction?1:-1:0}})}}applyFiltersAndSort(){this.state.data&&Array.isArray(this.state.data)?(this.applyFilters(),this.applySorting(),this.state.filteredData&&Array.isArray(this.state.filteredData)||(this.state.filteredData=[...this.state.data])):this.state.filteredData=[]}applyCurrentStyling(){this.applyFontSize(),this.applyCellPadding()}attachEvents(){this.tableContainer.addEventListener("click",e=>{if(e.target.closest(".sort-indicator")){const t=e.target.closest(".sort-indicator").dataset.column,a=this.state.sortState.column===t?this.state.sortState.direction:null;let n;n=null===a?"asc":"asc"===a?"desc":null,this.handleSort(t,n)}}),this.tableContainer.addEventListener("click",e=>{if(e.target.closest(".filter-btn")){const t=e.target.closest(".filter-btn").dataset.column;this.toggleFilter(t,e.target.closest(".filter-btn"))}}),this.tableContainer.addEventListener("click",e=>{if(e.target.matches("[data-page]")){const t=parseInt(e.target.dataset.page);this.handlePageChange(t)}}),this.tableContainer.addEventListener("change",e=>{e.target.matches(".page-size-select")&&(this.state.pageSize=parseInt(e.target.value),this.state.currentPage=1,this.renderBody(),this.renderPagination(),this.applyCurrentStyling(),this.autoSaveSettings())}),this.tableContainer.addEventListener("click",e=>{if(e.target.matches("[data-menu-toggle]")){const t=e.target.nextElementSibling;t.style.display="none"===t.style.display?"block":"none"}}),this.tableContainer.addEventListener("click",e=>{e.target.matches('[data-action="column-settings"]')&&this.showColumnSettings()}),this.tableContainer.addEventListener("click",e=>{e.target.matches('[data-action="clear-all-filters"]')&&(this.clearAllFilters(),e.target.closest(".table-menu-dropdown").style.display="none")}),this.tableContainer.addEventListener("click",e=>{e.target.matches('[data-action="clear-localstorage"]')&&(this.showLocalStorageClearConfirmation(),e.target.closest(".table-menu-dropdown").style.display="none")}),this.tableContainer.addEventListener("click",e=>{if(e.target.matches('[data-action="font-size"][data-size]')){const t=e.target.dataset.size;this.setFontSize(t),e.target.closest(".table-menu-dropdown").style.display="none"}}),this.tableContainer.addEventListener("click",e=>{if(e.target.matches('[data-action="cell-padding"][data-padding]')){const t=e.target.dataset.padding;this.setCellPadding(t),e.target.closest(".table-menu-dropdown").style.display="none"}}),this.attachResizeEvents()}attachResizeEvents(){let e=!1,t=null,a=0,n=0,i=0,s=null,l=null;const r=(e,t,a)=>{const n=(l||(l=document.createElement("div"),l.className="resize-tooltip",l.style.display="none",document.body.appendChild(l)),l),i=TableUtils.pxToCm(a);n.textContent=`${a}px (${i}cm)`;const s=TABLE_CONFIG.SIZES.TOOLTIP_OFFSET,r=TableUtils.constrainToViewport(n,e,t,s.x,-s.y);n.style.left=`${r.x}px`,n.style.top=`${r.y}px`,n.style.display="block",n.style.opacity="1"},o=()=>{l&&(l.style.opacity="0",setTimeout(()=>{l&&(l.style.display="none")},TABLE_CONFIG.TIMING.TOOLTIP_FADE))};this.tableContainer.addEventListener("mousedown",l=>{if(l.target.matches(".resize-handle")){const c=Date.now(),d=l.target.dataset.column;if(c-i<TABLE_CONFIG.SIZES.DOUBLE_CLICK_THRESHOLD&&s===d)return this.autoResizeColumn(d),o(),l.preventDefault(),i=0,void(s=null);i=c,s=d,e=!0,t=d,a=l.clientX,n=this.state.columnWidths[t],document.body.style.cursor="col-resize",r(l.clientX,l.clientY,n),l.preventDefault()}}),document.addEventListener("mousemove",i=>{if(e){const e=i.clientX-a,s=Math.max(TABLE_CONFIG.SIZES.MIN_COLUMN_WIDTH,n+e);this.handleColumnResize(t,s),r(i.clientX,i.clientY,s)}}),document.addEventListener("mouseup",()=>{e&&(e=!1,t=null,document.body.style.cursor="",o())})}autoResizeColumn(e){const t=this.options.columns.find(t=>t.key===e);if(!t)return;const a=document.createElement("div");a.style.position="absolute",a.style.visibility="hidden",a.style.whiteSpace="nowrap",a.style.fontSize=getComputedStyle(this.tableContainer).fontSize,a.style.fontFamily=getComputedStyle(this.tableContainer).fontFamily,a.style.padding="8px 12px",document.body.appendChild(a);let n=0;a.textContent=t.title||t.key;const i=a.offsetWidth+40;n=Math.max(n,i),this.getCurrentPageData().forEach(e=>{let i=e[t.dataIndex]||"";t.render&&"function"==typeof t.render&&(i=t.render(i,e),"string"==typeof i&&(i=i.replace(/<[^>]*>/g,""))),a.textContent=String(i);const s=a.offsetWidth;n=Math.max(n,s)}),document.body.removeChild(a);const s=Math.max(TABLE_CONFIG.SIZES.AUTO_RESIZE_MIN,Math.min(n,TABLE_CONFIG.SIZES.AUTO_RESIZE_MAX));this.handleColumnResize(e,s)}getCurrentPageData(){const e=(this.state.currentPage-1)*this.state.pageSize,t=e+this.state.pageSize;return this.state.filteredData.slice(e,t)}async saveSettings(){return!!this.settingsManager&&await this.settingsManager.saveSettings(this)}async loadSettings(){if(!this.settingsManager)return!1;const e=await this.settingsManager.loadSettings(this);return e&&this.render(),e}async clearSettings(){return!!this.settingsManager&&await this.settingsManager.clearSettings()}async autoSaveSettings(){this.settingsManager&&this.options.persistSettings&&(clearTimeout(this._autoSaveTimeout),this._autoSaveTimeout=setTimeout(async()=>{await this.saveSettings()},1e3))}toggleFilter(e,t){if(this.state.openFilter===e)return void this.closeFilter();this.closeFilter(),this.state.openFilter=e;const a=this.options.columns.find(t=>t.key===e);this.showFilterDropdown(a,t)}showFilterDropdown(e,t){const a=JSON.parse(JSON.stringify(this.state.filters)),n=JSON.parse(JSON.stringify(this.state.rangeFilters)),i=JSON.parse(JSON.stringify(this.state.dateRangeFilters)),s=[...this.state.filteredData],l=document.createElement("div");l.className="filter-dropdown-wrapper",l.style.position="fixed",l.style.zIndex="9999";const r=[...new Set(this.state.data.map(t=>t[e.dataIndex]))],o=this.state.filters[e.key]||[];let c="";c="date-hierarchy"===e.filterType?this.createDateHierarchyFilter(r,o,e):this.isNumericColumn(e)?this.createRangeFilter(e,r):this.createTextFilter(r,o,e),l.innerHTML=`\n <div class="filter-dropdown">\n <div class="filter-dropdown-header">\n <div class="filter-dropdown-title">${e.title}</div>\n </div>\n ${c}\n <div class="filter-dropdown-footer">\n <button class="filter-btn-clear-column">\n 🔄 この列のフィルタをクリア\n </button>\n <div class="filter-footer-actions">\n <button class="filter-btn-cancel">キャンセル</button>\n <button class="filter-btn-confirm">OK</button>\n </div>\n </div>\n </div>\n `,document.body.appendChild(l);const d=t.closest(".table-header"),h=d?d.getBoundingClientRect():t.getBoundingClientRect(),u=(l.getBoundingClientRect(),window.innerWidth),p=window.innerHeight,m=p-20-20;l.style.maxHeight=`${m}px`;const g=l.querySelector(".filter-dropdown-content, .date-filter-content");if(g){const e=m-(l.querySelector(".filter-dropdown-header")?.offsetHeight||0)-(l.querySelector(".filter-dropdown-search")?.offsetHeight||0)-(l.querySelector(".filter-dropdown-range")?.offsetHeight||0)-(l.querySelector(".filter-dropdown-footer")?.offsetHeight||0)-40;g.style.maxHeight=`${Math.max(200,e)}px`}const f=l.getBoundingClientRect();let y=h.right+5;y+f.width>u&&(y=h.left-f.width-5,y<5&&(y=5));let b=h.top+(h.height-f.height)/2;b<20?b=20:b+f.height>p-20&&(b=p-f.height-20),l.style.left=`${y}px`,l.style.top=`${b}px`,this.attachFilterEvents(l,e,a,n,i,s)}createTextFilter(e,t,a){const n=0===t.length,i=[...e].sort((e,t)=>null==e||""===e?1:null==t||""===t?-1:String(e).localeCompare(String(t)));return`\n <div class="filter-dropdown-search">\n <input type="text" class="filter-search-input" placeholder="検索...">\n </div>\n <div class="filter-dropdown-content">\n <div class="filter-option select-all">\n <label class="filter-option-label">\n <input type="checkbox" class="filter-checkbox" data-value="__all__" ${n?"checked":""}>\n <span class="filter-option-text">(すべて選択) <span class="filter-option-count">(${i.length})</span></span>\n </label>\n </div>\n <div class="filter-options-list">\n ${i.map(e=>`\n <div class="filter-option">\n <label class="filter-option-label">\n <input type="checkbox" class="filter-checkbox" data-value="${e}" ${n||t.includes(e)?"checked":""}>\n <span class="filter-option-text">${e||"(Empty)"} <span class="filter-option-count">(${this.countDataItems(a,e)})</span></span>\n </label>\n </div>\n `).join("")}\n </div>\n </div>\n `}countDataItems(e,t){return this.state.data&&e?"date-hierarchy"===e.filterType||this.isDateValue(t)?this.state.data.filter(a=>{const n=a[e.dataIndex];if(!n)return!1;const i=n.toString();if(4===t.length&&/^\d{4}$/.test(t))return i.startsWith(t);if(7===t.length&&/^\d{4}-\d{2}$/.test(t))return i.startsWith(t);if(t.includes("-"))return i===t;{const a=this.getCurrentYearContext(e,t);return!!a&&i.startsWith(a)}}).length:this.state.data.filter(a=>a[e.dataIndex]==t).length:0}isDateValue(e){return"string"==typeof e&&/^\d{4}(-\d{2})?(-\d{2})?$/.test(e)}countDataItemsForMonth(e,t,a){return this.state.data&&e?this.state.data.filter(n=>{const i=n[e.dataIndex];if(!i)return!1;const s=new Date(i),l=s.getFullYear(),r=s.toLocaleString("default",{month:"long"});return l==t&&r===a}).length:0}getCurrentYearContext(e,t){const a=this.state.data.find(a=>{const n=a[e.dataIndex];return!!n&&new Date(n).toLocaleString("default",{month:"long"})===t});if(a){const t=new Date(a[e.dataIndex]);return`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}`}return null}createRangeFilter(e,t){const a=t.filter(e=>!isNaN(e)).map(Number),n=Math.min(...a),i=Math.max(...a),s=TableUtils.getDecimalPlaces(t.filter(e=>!isNaN(e)).map(Number)),l=this.state.rangeFilters[e.key]||{min:n,max:i},r=this.state.filters[e.key]||[],o=[...t].sort((e,t)=>Number(e)-Number(t)),c=0===r.length,d=(0===r.length&&this.state.filters[e.key]&&0===this.state.filters[e.key].length&&this.hasActiveFilter(e.key),r.length>0&&r.length<o.length);let h="",u="";return c&&!this.hasActiveFilter(e.key)?u="checked":d&&(h="indeterminate"),`\n <div class="filter-dropdown-range">\n <div class="range-filter">\n <div class="range-inputs">\n <div class="range-input-group">\n <label class="range-label">Min</label>\n <input type="number" class="range-input range-min" value="${TableUtils.formatNumberValue(l.min,s)}" min="${n}" max="${i}" data-min="${n}" data-max="${i}" data-decimal-places="${s}">\n </div>\n <div class="range-input-group">\n <label class="range-label">Max</label>\n <input type="number" class="range-input range-max" value="${TableUtils.formatNumberValue(l.max,s)}" min="${n}" max="${i}" data-min="${n}" data-max="${i}" data-decimal-places="${s}">\n </div>\n </div>\n <div class="range-slider-container">\n <div class="range-slider" data-min="${n}" data-max="${i}">\n <div class="range-track"></div>\n <div class="range-thumb range-thumb-min" data-type="min"></div>\n <div class="range-thumb range-thumb-max" data-type="max"></div>\n </div>\n </div>\n </div>\n </div>\n <div class="filter-dropdown-search">\n <input type="text" class="filter-search-input" placeholder="検索...">\n </div>\n <div class="filter-dropdown-content">\n <div class="filter-option select-all">\n <label class="filter-option-label">\n <input type="checkbox" class="filter-checkbox" data-value="__all__" ${u} data-state="${h}">\n <span class="filter-option-text">(すべて選択) <span class="filter-option-count">(${o.length})</span></span>\n </label>\n </div>\n <div class="filter-options-list">\n ${o.map(t=>`\n <div class="filter-option">\n <label class="filter-option-label">\n <input type="checkbox" class="filter-checkbox" data-value="${t}" ${0===r.length&&!this.hasActiveFilter(e.key)||r.includes(Number(t))?"checked":""}>\n <span class="filter-option-text">${t} <span class="filter-option-count">(${this.countDataItems(e,t)})</span></span>\n </label>\n </div>\n `).join("")}\n </div>\n </div>\n `}createDateHierarchyFilter(e,t,a){const n=this.groupDatesByHierarchy(e),i=0===t.length,s=e.filter(e=>e&&!isNaN(new Date(e).getTime())),l=s.length>0?new Date(Math.min(...s.map(e=>new Date(e).getTime()))).toISOString().split("T")[0]:"",r=s.length>0?new Date(Math.max(...s.map(e=>new Date(e).getTime()))).toISOString().split("T")[0]:"",o=this.state.dateRangeFilters[a?.key]||{startDate:l,endDate:r};return`\n <div class="filter-dropdown-range">\n <div class="date-range-filter">\n <div class="date-range-inputs">\n <div class="date-range-input-group">\n <label class="date-range-label">開始日</label>\n <input type="date" class="date-range-input date-range-min" value="${o.startDate||l}" min="${l}" max="${r}">\n </div>\n <div class="date-range-input-group">\n <label class="date-range-label">終了日</label>\n <input type="date" class="date-range-input date-range-max" value="${o.endDate||r}" min="${l}" max="${r}">\n </div>\n </div>\n <div class="date-range-slider-container">\n <div class="date-range-slider" data-min="${l}" data-max="${r}">\n <div class="date-range-track"></div>\n <div class="date-range-thumb date-range-thumb-min" data-type="min"></div>\n <div class="date-range-thumb date-range-thumb-max" data-type="max"></div>\n </div>\n </div>\n </div>\n </div>\n <div class="filter-dropdown-search">\n <input type="text" class="filter-search-input" placeholder="検索...">\n </div>\n <div class="date-expand-controls">\n <div class="expand-control-header">\n <span class="expand-control-label">展開表示:</span>\n <div class="expand-control-buttons">\n <button class="expand-control-btn active" data-level="year">年</button>\n <button class="expand-control-btn" data-level="month">月</button>\n <button class="expand-control-btn" data-level="day">日</button>\n </div>\n </div>\n </div>\n <div class="filter-dropdown-content">\n <div class="filter-option select-all">\n <label class="filter-option-label">\n <input type="checkbox" class="filter-checkbox" data-value="__all__" ${i?"checked":""}>\n <span class="filter-option-text">(すべて選択) <span class="filter-option-count">(${e.length})</span></span>\n </label>\n </div>\n <div class="date-filter-content">\n ${Object.keys(n).map(e=>`\n <div class="date-filter-group">\n <div class="date-filter-group-header">\n <button class="expand-btn">▼</button>\n <label class="date-filter-label">\n <input type="checkbox" class="date-filter-checkbox" data-value="${e}" ${i||this.isYearSelected(e,n[e],t)?"checked":""}>\n <span class="date-filter-text">${e} <span class="filter-option-count">(${this.countDataItems(a,e)})</span></span>\n </label>\n </div>\n <div class="date-filter-children">\n ${Object.keys(n[e]).map(s=>`\n <div class="date-filter-item date-filter-month">\n <div class="date-filter-month-header">\n <button class="expand-btn expand-btn-month">▼</button>\n <label class="date-filter-label">\n <input type="checkbox" class="date-filter-checkbox" data-value="${e}-${s}" ${i||this.isMonthSelected(e,s,n[e][s],t)?"checked":""}>\n <span class="date-filter-text">${s} <span class="filter-option-count">(${this.countDataItemsForMonth(a,e,s)})</span></span>\n </label>\n </div>\n <div class="date-filter-days">\n ${n[e][s].map(e=>`\n <div class="date-filter-item date-filter-day">\n <label class="date-filter-label">\n <input type="checkbox" class="date-filter-checkbox" data-value="${e}" ${i||t.includes(e)?"checked":""}>\n <span class="date-filter-text">${new Date(e).getDate()}日 <span class="filter-option-count">(${this.countDataItems(a,e)})</span></span>\n </label>\n </div>\n `).join("")}\n </div>\n </div>\n `).join("")}\n </div>\n </div>\n `).join("")}\n </div>\n </div>\n `}attachFilterEvents(e,t,a,n,i,s){const l=()=>{if(this.isNumericColumn(t)){const a=Array.from(e.querySelectorAll(".filter-checkbox:checked")).map(e=>e.dataset.value).filter(e=>"__all__"!==e).map(e=>parseFloat(e)).filter(e=>!isNaN(e));delete this.state.rangeFilters[t.key];const n=e.querySelectorAll('.filter-checkbox:not([data-value="__all__"])');a.length===n.length?this.handleFilter(t.key,[]):this.handleFilter(t.key,a)}else if("date-hierarchy"===t.filterType){const a=Array.from(e.querySelectorAll(".filter-checkbox:checked, .date-filter-checkbox:checked")).map(e=>e.dataset.value).filter(e=>"__all__"!==e),n=e.querySelector(".date-range-min"),i=e.querySelector(".date-range-max");n&&i&&(this.state.dateRangeFilters[t.key]={startDate:n.value,endDate:i.value});const s=e.querySelectorAll('.filter-checkbox:not([data-value="__all__"]), .date-filter-checkbox');a.length===s.length?this.handleFilter(t.key,[]):this.handleFilter(t.key,a)}else{const a=Array.from(e.querySelectorAll(".filter-checkbox:checked")).map(e=>e.dataset.value).filter(e=>"__all__"!==e),n=e.querySelectorAll('.filter-checkbox:not([data-value="__all__"])');a.length===n.length?this.handleFilter(t.key,[]):this.handleFilter(t.key,a)}this.updateFilterButtonState(t.key)};e.querySelector(".filter-btn-cancel").addEventListener("click",()=>{this.state.filters=a,this.state.rangeFilters=n,this.state.dateRangeFilters=i,this.state.filteredData=s,this.state.currentPage=1,this.renderBody(),this.renderPagination(),this.closeFilter(),this.updateFilterButtonState(t.key)});const r=e.querySelector(".filter-btn-clear-column");r&&r.addEventListener("click",()=>{this.state.filters[t.key]=[],this.state.rangeFilters[t.key]=null,this.render(),this.closeFilter(),this.autoSaveSettings()}),e.querySelector(".filter-btn-confirm").addEventListener("click",()=>{if(this.isNumericColumn(t)){const a=Array.from(e.querySelectorAll(".filter-checkbox:checked")).map(e=>e.dataset.value).filter(e=>"__all__"!==e).map(e=>parseFloat(e)).filter(e=>!isNaN(e));delete this.state.rangeFilters[t.key];const n=e.querySelectorAll('.filter-checkbox:not([data-value="__all__"])');a.length===n.length?this.handleFilter(t.key,[]):this.handleFilter(t.key,a)}else if("date-hierarchy"===t.filterType){const a=Array.from(e.querySelectorAll(".filter-checkbox:checked, .date-filter-checkbox:checked")).map(e=>e.dataset.value).filter(e=>"__all__"!==e),n=e.querySelector(".date-range-min"),i=e.querySelector(".date-range-max");n&&i&&(this.state.dateRangeFilters[t.key]={startDate:n.value,endDate:i.value});const s=e.querySelectorAll('.filter-checkbox:not([data-value="__all__"]), .date-filter-checkbox');a.length===s.length?this.handleFilter(t.key,[]):this.handleFilter(t.key,a)}else{const a=Array.from(e.querySelectorAll(".filter-checkbox:checked")).map(e=>e.dataset.value).filter(e=>"__all__"!==e),n=e.querySelectorAll('.filter-checkbox:not([data-value="__all__"])');a.length===n.length?this.handleFilter(t.key,[]):this.handleFilter(t.key,a)}this.updateFilterButtonState(t.key),this.closeFilter()});const o=()=>{const t=e.querySelector(".filter-btn-confirm");if(!t)return;const a=e.querySelectorAll('.filter-checkbox:checked:not([data-value="__all__"]), .date-filter-checkbox:checked').length>0;t.disabled=!a},c=e.querySelector('[data-value="__all__"]');c&&c.addEventListener("change",t=>{e.querySelectorAll('.filter-checkbox:not([data-value="__all__"]), .date-filter-checkbox').forEach(e=>e.checked=t.target.checked),t.target.indeterminate=!1,o(),l()});let d='.filter-checkbox:not([data-value="__all__"])';"date-hierarchy"!==t.filterType&&(d+=", .date-filter-checkbox");const h=e.querySelectorAll(d);h.forEach(a=>{a.addEventListener("change",()=>{this.isNumericColumn(t)?this.updateRangeFromCheckbox(e,t):c&&(e.querySelectorAll('.filter-checkbox:not([data-value="__all__"]), .date-filter-checkbox'),e.querySelectorAll('.filter-checkbox:checked:not([data-value="__all__"]), .date-filter-checkbox:checked'),TableUtils.updateSelectAllState(c,h)),o(),l()})}),o();const u=e.querySelector(".filter-search-input");u&&u.addEventListener("input",t=>{const a=t.target.value.toLowerCase();e.querySelectorAll(".filter-option:not(.select-all), .date-filter-group, .date-filter-item").forEach(e=>{const t=e.querySelector(".filter-option-text, .date-filter-text")?.textContent.toLowerCase()||"";e.style.display=t.includes(a)?"block":"none"})}),this.attachRangeSliderEvents(e,t,l),this.attachDateRangeSliderEvents(e,t,l),"date-hierarchy"===t.filterType&&this.attachDateHierarchyEvents(e,t,l),this.updateSelectAllCheckboxState(e,t)}updateSelectAllCheckboxState(e,t){const a=e.querySelector('[data-value="__all__"]');if(!a)return;const n=e.querySelectorAll('.filter-checkbox:not([data-value="__all__"]), .date-filter-checkbox'),i=e.querySelectorAll('.filter-checkbox:checked:not([data-value="__all__"]), .date-filter-checkbox:checked');"indeterminate"===a.dataset.state?(a.indeterminate=!0,a.checked=!1):0===i.length?(a.checked=!1,a.indeterminate=!1):i.length===n.length?(a.checked=!0,a.indeterminate=!1):(a.checked=!1,a.indeterminate=!0)}updateSliderFromCheckboxes(e,t){if(!this.isNumericColumn(t))return;const a=e.querySelector(".range-min"),n=e.querySelector(".range-max");if(!a||!n)return;const i=e.querySelectorAll('.filter-checkbox:checked:not([data-value="__all__"])');if(0===i.length)return;const s=Array.from(i).map(e=>parseFloat(e.dataset.value)).filter(e=>!isNaN(e));if(s.length>0){const e=Math.min(...s),t=Math.max(...s),i=parseInt(a.dataset.decimalPlaces)||0;a.value=TableUtils.formatNumberValue(e,i),n.value=TableUtils.formatNumberValue(t,i)}}updateDateSliderFromCheckboxes(e,t){const a=e.querySelector(".date-range-min"),n=e.querySelector(".date-range-max");if(!a||!n)return;const i=e.querySelectorAll(".date-filter-checkbox:checked");if(0===i.length)return;const s=Array.from(i).map(e=>e.dataset.value).filter(e=>e&&"__all__"!==e&&/^\d{4}-\d{2}-\d{2}$/.test(e)).map(e=>new Date(e)).filter(e=>!isNaN(e.getTime()));if(s.length>0){const e=new Date(Math.min(...s)),t=new Date(Math.max(...s));a.value=e.toISOString().split("T")[0],n.value=t.toISOString().split("T")[0]}}updateDateCheckboxFromRange(e,t){const a=e.querySelector(".date-range-min"),n=e.querySelector(".date-range-max");if(!a||!n)return;const i=new Date(a.value),s=new Date(n.value);isNaN(i.getTime())||isNaN(s.getTime())||(e.querySelectorAll(".date-filter-checkbox").forEach(e=>{const t=e.dataset.value;if(t&&"__all__"!==t)if(/^\d{4}-\d{2}-\d{2}$/.test(t)){const a=new Date(t);e.checked=a>=i&&a<=s}else if(/^\d{4}$/.test(t))parseInt(t),i.getFullYear(),s.getFullYear();else if(/^\d{4}-.+$/.test(t)){const[e,a]=t.split("-");new Date(Date.parse(a+" 1, 2000")).getMonth()}}),this.updateDateParentCheckboxes(e))}updateHierarchicalCheckboxState(e,t){if(!e||!t||0===t.length)return;const a=Array.from(t).filter(e=>e.checked).length;0===a?(e.checked=!1,e.indeterminate=!1):a===t.length?(e.checked=!0,e.indeterminate=!1):(e.checked=!1,e.indeterminate=!0)}propagateCheckboxStateToChildren(e,t){if(!e||!t)return;const a=e.checked;Array.from(t).forEach(e=>{e.checked=a,e.indeterminate=!1}),e.indeterminate=!1}updateDateParentCheckboxes(e){e.querySelectorAll(".date-filter-month").forEach(e=>{const t=e.querySelector(".date-filter-checkbox"),a=e.querySelectorAll(".date-filter-days .date-filter-checkbox");this.updateHierarchicalCheckboxState(t,a)}),e.querySelectorAll(".date-filter-group").forEach(e=>{const t=e.querySelector(".date-filter-checkbox"),a=e.querySelectorAll(".date-filter-children .date-filter-checkbox");this.updateHierarchicalCheckboxState(t,a)})}handleDateHierarchyCheckboxChange(e,t,a){const n=e.target;switch(this.getDateHierarchyLevel(n)){case"year":this.handleYearCheckboxChange(n,t);break;case"month":this.handleMonthCheckboxChange(n,t);break;case"day":this.handleDayCheckboxChange(n,t)}a&&"function"==typeof a&&setTimeout(()=>a(),0)}getDateHierarchyLevel(e){return e.closest(".date-filter-group-header")?"year":e.closest(".date-filter-month-header")?"month":e.closest(".date-filter-day")?"day":"unknown"}handleYearCheckboxChange(e,t){const a=e.closest(".date-filter-group"),n=a.querySelectorAll(".date-filter-month .date-filter-checkbox"),i=a.querySelectorAll(".date-filter-day .date-filter-checkbox");this.propagateCheckboxStateToChildren(e,n),this.propagateCheckboxStateToChildren(e,i)}handleMonthCheckboxChange(e,t){const a=e.closest(".date-filter-month").querySelectorAll(".date-filter-day .date-filter-checkbox");this.propagateCheckboxStateToChildren(e,a),this.updateDateParentCheckboxes(t)}handleDayCheckboxChange(e,t){this.updateDateParentCheckboxes(t)}closeFilter(){const e=document.querySelector(".filter-dropdown-wrapper");e&&e.remove(),this.state.openFilter=null}showColumnSettings(){const e=document.createElement("div");e.className="column-settings-overlay";const t=document.createElement("div");t.className="column-settings-modal";let a=`\n <div class="column-settings-header">\n <h3>列設定</h3>\n <button class="close-btn">×</button>\n </div>\n <div class="column-settings-content">\n <div class="column-list">\n <div class="column-item select-all-item">\n <label class="column-label">\n <input type="checkbox" class="column-checkbox-all" id="select-all-columns">\n <span class="column-text">(すべて選択)</span>\n </label>\n </div>\n ${this.options.columns.map(e=>`\n <div class="column-item">\n <label class="column-label">\n <input type="checkbox" class="column-checkbox" data-column="${e.key}" ${this.state.visibleColumns[e.key]?"checked":""}>\n <span class="column-text">${e.title}</span>\n </label>\n <label class="pin-label">\n <input type="checkbox" class="pin-checkbox" data-column="${e.key}" ${this.state.pinnedColumns[e.key]?"checked":""}>\n <span class="pin-icon">📌</span>\n </label>\n </div>\n `).join("")}\n </div>\n </div>\n <div class="column-settings-footer">\n <button class="cancel-btn">キャンセル</button>\n <button class="confirm-btn">OK</button>\n </div>\n `;t.innerHTML=a,e.appendChild(t),document.body.appendChild(e),e.querySelector(".close-btn").addEventListener("click",()=>e.remove()),e.querySelector(".cancel-btn").addEventListener("click",()=>e.remove());const n=e.querySelector(".column-checkbox-all"),i=e.querySelectorAll(".column-checkbox"),s=()=>{const e=Array.from(i).filter(e=>e.checked).length,t=i.length;0===e?(n.checked=!1,n.indeterminate=!1):e===t?(n.checked=!0,n.indeterminate=!1):(n.checked=!1,n.indeterminate=!0)};s(),n.addEventListener("change",e=>{i.forEach(t=>t.checked=e.target.checked),s()}),i.forEach(e=>{e.addEventListener("change",s)}),e.querySelector(".confirm-btn").addEventListener("click",()=>{e.querySelectorAll(".column-checkbox").forEach(e=>{this.state.visibleColumns[e.dataset.column]=e.checked}),e.querySelectorAll(".pin-checkbox").forEach(e=>{this.state.pinnedColumns[e.dataset.column]=e.checked}),this.render(),e.remove()}),e.addEventListener("click",t=>{t.target===e&&e.remove()})}attachRangeSliderEvents(e,t,a){const n=e.querySelector(".range-slider"),i=e.querySelector(".range-track"),s=e.querySelector(".range-thumb-min"),l=e.querySelector(".range-thumb-max"),r=e.querySelector(".range-min"),o=e.querySelector(".range-max");if(!(n&&i&&s&&l&&r&&o))return;const c=parseFloat(n.dataset.min),d=parseFloat(n.dataset.max)-c,h=()=>{const e=parseFloat(r.value),t=parseFloat(o.value),a=(e-c)/d*100,n=(t-c)/d*100;s.style.left=`${a}%`,l.style.left=`${n}%`,i.style.left=`${a}%`,i.style.width=n-a+"%"};this.updateSliderFromCheckboxes(e,t),h(),r.addEventListener("input",()=>{n._isUpdatingFromSlider||(h(),this.updateCheckboxFromRange(e,t),a())}),o.addEventListener("input",()=>{n._isUpdatingFromSlider||(h(),this.updateCheckboxFromRange(e,t),a())});let u=!1,p=null;const m=(e,t)=>{u=!0,p=t,e.preventDefault()},g=i=>{if(!u||!p)return;const s=n.getBoundingClientRect(),l=(i.clientX||(i.touches&&i.touches[0]?i.touches[0].clientX:0))-s.left,m=Math.max(0,Math.min(100,l/s.width*100)),g=c+m/100*d,f=parseInt(r.dataset.decimalPlaces)||0;if("min"===p.dataset.type){const e=parseFloat(o.value),t=Math.min(g,e);r.value=TableUtils.formatNumberValue(t,f)}else{const e=parseFloat(r.value),t=Math.max(g,e);o.value=TableUtils.formatNumberValue(t,f)}h(),this.updateCheckboxFromRange(e,t),a()},f=()=>{u=!1,p=null};s.addEventListener("mousedown",e=>m(e,s)),l.addEventListener("mousedown",e=>m(e,l)),document.addEventListener("mousemove",g),document.addEventListener("mouseup",f),s.addEventListener("touchstart",e=>m(e,s),{passive:!1}),l.addEventListener("touchstart",e=>m(e,l),{passive:!1}),document.addEventListener("touchmove",g,{passive:!1}),document.addEventListener("touchend",f)}updateRangeFromCheckbox(e,t){const a=e.querySelector(".range-min"),n=e.querySelector(".range-max"),i=e.querySelector(".range-slider");if(!a||!n||!i)return;const s=e.querySelectorAll('.filter-checkbox:not([data-value="__all__"]):checked'),l=e.querySelector('[data-value="__all__"]');if(l){const t=e.querySelectorAll('.filter-checkbox:not([data-value="__all__"])');TableUtils.updateSelectAllState(l,t)}if(0===s.length){const t=parseFloat(i.dataset.min),s=parseFloat(i.dataset.max);i._isUpdatingFromSlider=!0,a.value=t,n.value=s;const l=e.querySelector(".range-track"),r=e.querySelector(".range-thumb-min"),o=e.querySelector(".range-thumb-max");l&&r&&o&&(r.style.left="0%",o.style.left="100%",l.style.left="0%",l.style.width="100%"),setTimeout(()=>{i._isUpdatingFromSlider=!1},0)}else{const t=Array.from(s).map(e=>parseFloat(e.dataset.value)),l=Math.min(...t),r=Math.max(...t),o=parseFloat(a.value),c=parseFloat(n.value);if(l!==o||r!==c){i._isUpdatingFromSlider=!0,a.value=l,n.value=r;const t=parseFloat(i.dataset.min),s=parseFloat(i.dataset.max)-t,o=(l-t)/s*100,c=(r-t)/s*100,d=e.querySelector(".range-track"),h=e.querySelector(".range-thumb-min"),u=e.querySelector(".range-thumb-max");d&&h&&u&&(h.style.left=`${o}%`,u.style.left=`${c}%`,d.style.left=`${o}%`,d.style.width=c-o+"%"),setTimeout(()=>{i._isUpdatingFromSlider=!1},0)}}}updateCheckboxFromRange(e,t){const a=e.querySelector(".range-min"),n=e.querySelector(".range-max");if(!a||!n)return;const i=parseFloat(a.value),s=parseFloat(n.value);e.querySelectorAll('.filter-checkbox:not([data-value="__all__"])').forEach(e=>{const t=parseFloat(e.dataset.value);isNaN(t)||(e.checked=t>=i&&t<=s)});const l=e.querySelector('[data-value="__all__"]');if(l){const t=e.querySelectorAll('.filter-checkbox:not([data-value="__all__"]):checked').length,a=e.querySelectorAll('.filter-checkbox:not([data-value="__all__"])').length;0===t?(l.checked=!1,l.indeterminate=!1):t===a?(l.checked=!0,l.indeterminate=!1):(l.checked=!1,l.indeterminate=!0)}}attachDateRangeSliderEvents(e,t,a){const n=e.querySelector(".date-range-slider"),i=e.querySelector(".date-range-track"),s=e.querySelector(".date-range-thumb-min"),l=e.querySelector(".date-range-thumb-max"),r=e.querySelector(".date-range-min"),o=e.querySelector(".date-range-max");if(!(n&&i&&s&&l&&r&&o))return;const c=new Date(n.dataset.min).getTime(),d=new Date(n.dataset.max).getTime()-c,h=()=>{const e=new Date(r.value).getTime(),t=new Date(o.value).getTime(),a=(e-c)/d*100,n=(t-c)/d*100;s.style.left=`${a}%`,l.style.left=`${n}%`,i.style.left=`${a}%`,i.style.width=n-a+"%"};this.updateDateSliderFromCheckboxes(e,t),h(),r.addEventListener("input",()=>{h(),this.updateDateCheckboxFromRange(e,t),a()}),o.addEventListener("input",()=>{h(),this.updateDateCheckboxFromRange(e,t),a()});let u=!1,p=null;const m=(e,t)=>{u=!0,p=t,e.preventDefault()},g=i=>{if(!u||!p)return;const s=n.getBoundingClientRect(),l=(i.clientX||(i.touches&&i.touches[0]?i.touches[0].clientX:0))-s.left,m=Math.max(0,Math.min(100,l/s.width*100)),g=new Date(c+m/100*d).toISOString().split("T")[0];if("min"===p.dataset.type){const e=o.value,t=g<=e?g:e;r.value=t}else{const e=r.value,t=g>=e?g:e;o.value=t}h(),this.updateDateCheckboxFromRange(e,t),a()},f=()=>{u=!1,p=null};s.addEventListener("mousedown",e=>m(e,s)),l.addEventListener("mousedown",e=>m(e,l)),document.addEventListener("mousemove",g),document.addEventListener("mouseup",f),s.addEventListener("touchstart",e=>m(e,s),{passive:!1}),l.addEventListener("touchstart",e=>m(e,l),{passive:!1}),document.addEventListener("touchmove",g,{passive:!1}),document.addEventListener("touchend",f)}attachDateHierarchyEvents(e,t,a){const n=e.querySelectorAll(".expand-control-btn"),i=(e.querySelector(".date-filter-content"),t=>{const a=e.querySelectorAll(".date-filter-group"),i=e.querySelectorAll(".date-filter-month"),s=e.querySelectorAll(".date-filter-day");n.forEach(e=>e.classList.remove("active"));const l=e.querySelector(`[data-level="${t}"]`);switch(l&&l.classList.add("active"),t){case"year":a.forEach(e=>{e.style.display="block",e.querySelector(".date-filter-children").style.display="none",e.querySelector(".expand-btn").textContent="▶"});break;case"month":a.forEach(e=>{e.style.display="block",e.querySelector(".date-filter-children").style.display="block",e.querySelector(".expand-btn").textContent="▼"}),i.forEach(e=>{e.style.display="block",e.querySelector(".date-filter-days").style.display="none",e.querySelector(".expand-btn-month").textContent="▶"});break;case"day":a.forEach(e=>{e.style.display="block",e.querySelector(".date-filter-children").style.display="block",e.querySelector(".expand-btn").textContent="▼"}),i.forEach(e=>{e.style.display="block",e.querySelector(".date-filter-days").style.display="block",e.querySelector(".expand-btn-month").textContent="▼"}),s.forEach(e=>{e.style.display="block"})}});n.forEach(e=>{e.addEventListener("click",t=>{t.preventDefault(),t.stopPropagation(),i(e.dataset.level)})}),i("year"),e.querySelectorAll(".expand-btn:not(.expand-btn-month)").forEach(e=>{e.addEventListener("click",t=>{t.stopPropagation();const a=e.closest(".date-filter-group").querySelector(".date-filter-children");"none"!==a.style.display&&a.style.display?(a.style.display="none",e.textContent="▶"):(a.style.display="block",e.textContent="▼")})}),e.querySelectorAll(".expand-btn-month").forEach(e=>{e.addEventListener("click",t=>{t.stopPropagation();const a=e.closest(".date-filter-month").querySelector(".date-filter-days");"none"!==a.style.display&&a.style.display?(a.style.display="none",e.textContent="▶"):(a.style.display="block",e.textContent="▼")})}),void 0===this._applyRealtimeOnDateChange&&(this._applyRealtimeOnDateChange=!0),e.querySelectorAll(".date-filter-checkbox").forEach(t=>{t.addEventListener("change",t=>{const n=this._applyRealtimeOnDateChange;this._applyRealtimeOnDateChange=!1,this.handleDateHierarchyCheckboxChange(t,e,()=>{this._applyRealtimeOnDateChange=n,this._applyRealtimeOnDateChange&&a&&a()})})})}handleRangeFilter(e,t,a){const n=this.options.columns.find(t=>t.key===e);n&&(this.state.filteredData=this.state.data.filter(e=>{const i=parseFloat(e[n.dataIndex]);return!isNaN(i)&&i>=t&&i<=a}),this.state.currentPage=1,this.applySorting(),this.renderBody(),this.renderPagination())}isYearSelected(e,t,a){return 0===a.length||!!a.includes(e)||!!Object.keys(t).some(t=>a.includes(`${e}-${t}`))||a.some(t=>!("string"!=typeof t||!t.includes("-"))&&t.startsWith(`${e}-`))}isMonthSelected(e,t,a,n){return 0===n.length||!!n.includes(e)||n.includes(`${e}-${t}`)}isNumericColumn(e){const t=this.state.data[0]?.[e.dataIndex];return"number"==typeof t||!isNaN(Number(t))}groupDatesByHierarchy(e){const t={};e.forEach(e=>{if(e){const a=new Date(e),n=a.getFullYear(),i=String(a.getMonth()+1).padStart(2,"0"),s=a.toLocaleString("default",{month:"long"});t[n]||(t[n]={}),t[n][i]||(t[n][i]={displayName:s,dates:[]}),t[n][i].dates.push(e)}});const a={};return Object.keys(t).sort((e,t)=>parseInt(e)-parseInt(t)).forEach(e=>{a[e]={},Object.keys(t[e]).sort((e,t)=>parseInt(e)-parseInt(t)).forEach(n=>{const i=t[e][n],s=i.displayName;a[e][s]=i.dates.sort((e,t)=>new Date(e)-new Date(t))})}),a}setData(e){this.state.data=[...e],this.state.currentPage=1,this.render()}updateData(e){this.setData(e)}getFilters(){return{...this.state.filters}}setFilters(e){this.state.filters={...e},this.render()}clearFilters(){this.state.filters={},this.render()}clearAllFilters(){this.state.filters={},this.state.rangeFilters={},this.state.dateRangeFilters={},this.render(),this.autoSaveSettings()}setFontSize(e){this.state.fontSize=e,this.renderMenu(),this.applyCurrentStyling(),this.autoSaveSettings()}setCellPadding(e){this.state.cellPadding=e,this.renderMenu(),this.applyCurrentStyling(),this.autoSaveSettings()}applyFontSize(){const e=TABLE_CONFIG.FONT_SIZES[this.state.fontSize]||TABLE_CONFIG.FONT_SIZES.medium;this.container.style.setProperty("--table-font-size",e),this.tableContainer&&(this.tableContainer.style.setProperty("font-size",e,"important"),this.tableContainer.querySelectorAll("\n .table, \n .table-cell, \n .table-header, \n .header-content, \n .header-title,\n .enhanced-table-pagination,\n .pagination-info,\n .page-info,\n tbody tr td\n ").forEach(t=>{t.style.setProperty("font-size",e,"important")}),this.container.querySelectorAll("\n .table-menu-container,\n .table-menu-btn,\n .table-menu-dropdown,\n .table-menu-item,\n .table-submenu-dropdown\n ").forEach(t=>{t.style.setProperty("font-size",e,"important")}),document.querySelectorAll("\n .filter-dropdown-wrapper,\n .filter-dropdown,\n .filter-dropdown-header,\n .filter-dropdown-title,\n .filter-dropdown-search,\n .filter-search-input,\n .filter-dropdown-content,\n .filter-option,\n .filter-option-label,\n .filter-option-text,\n .filter-dropdown-footer,\n .filter-btn-confirm,\n .filter-btn-cancel,\n .filter-btn-clear-column,\n .range-filter,\n .range-label,\n .range-input,\n .date-range-filter,\n .date-range-label,\n .date-range-input,\n .date-filter-content,\n .date-filter-label,\n .date-filter-text,\n .date-expand-controls,\n .expand-control-label,\n .expand-control-btn\n ").forEach(t=>{t.style.setProperty("font-size",e,"important")}),document.querySelectorAll("\n .column-settings-overlay,\n .column-settings-modal,\n .column-settings-header,\n .column-settings-content,\n .column-list,\n .column-item,\n .column-label,\n .column-text,\n .column-settings-footer,\n .confirm-btn,\n .cancel-btn\n ").forEach(t=>{t.style.setProperty("font-size",e,"important")}))}applyCellPadding(){const e=TABLE_CONFIG.CELL_PADDING[this.state.cellPadding]||TABLE_CONFIG.CELL_PADDING.standard,t=`${e.vertical} ${e.horizontal}`;if(!this.tableContainer)return;const a=`${Math.round(.6*parseInt(e.vertical))+"px"} ${Math.round(.8*parseInt(e.horizontal))+"px"}`;this.tableContainer.querySelectorAll(".table-cell").forEach(e=>{e.style.setProperty("padding",t,"important")}),this.tableContainer.querySelectorAll(".header-content").forEach(e=>{e.style.setProperty("padding",t,"important")}),this.container.querySelectorAll("\n .table-menu-item,\n .table-menu-btn\n ").forEach(e=>{e.style.setProperty("padding",a,"important")}),document.querySelectorAll("\n .filter-option,\n .filter-btn-confirm,\n .filter-btn-cancel,\n .filter-btn-clear-column,\n .range-input,\n .date-range-input,\n .filter-search-input,\n .expand-control-btn\n ").forEach(e=>{e.style.setProperty("padding",a,"important")}),document.querySelectorAll("\n .filter-dropdown-header,\n .filter-dropdown-search,\n .filter-dropdown-footer,\n .date-expand-controls\n ").forEach(e=>{e.style.setProperty("padding",t,"important")}),this.tableContainer.querySelectorAll("\n .enhanced-table-pagination,\n .pagination-controls button\n ").forEach(e=>{e.style.setProperty("padding",a,"important")}),document.querySelectorAll("\n .column-item,\n .confirm-btn,\n .cancel-btn\n ").forEach(e=>{e.style.setProperty("padding",a,"important")})}getSelectedRows(){return[]}addTooltipToCell(e,t){if(!e||!t)return;const a="string"==typeof t?t:String(t);let n=null;const i=t=>{if(e.scrollWidth<=e.clientWidth&&e.scrollHeight<=e.clientHeight)return;n=document.createElement("div"),n.className="cell-tooltip",n.textContent=a,document.body.appendChild(n);const i=e.getBoundingClientRect(),s=n.getBoundingClientRect();let l=i.left+window.scrollX,r=i.bottom+window.scrollY+5;l+s.width>window.innerWidth&&(l=window.innerWidth-s.width-10),l<10&&(l=10),r+s.height>window.innerHeight+window.scrollY&&(r=i.top+window.scrollY-s.height-5),n.style.left=`${l}px`,n.style.top=`${r}px`},s=()=>{n&&(document.body.removeChild(n),n=null)};e.addEventListener("mouseenter",i),e.addEventListener("mouseleave",s),e._tooltipCleanup=()=>{e.removeEventListener("mouseenter",i),e.removeEventListener("mouseleave",s),s()}}showLocalStorageClearConfirmation(){const e=document.createElement("div");e.className="modal-overlay",e.style.cssText="\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n justify-content: center;\n align-items: center;\n z-index: 10000;\n ";const t=document.createElement("div");t.className="modal-content",t.style.cssText="\n background: white;\n padding: 24px;\n border-radius: 8px;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n max-width: 400px;\n margin: 20px;\n ",t.innerHTML='\n <h3 style="margin: 0 0 16px 0; color: #333;">LocalStorage クリア確認</h3>\n <p style="margin: 0 0 20px 0; line-height: 1.5; color: #666;">\n ExceLikeTable関連のLocalStorage情報を削除し、設定をデフォルトに戻します。よろしいですか?\n </p>\n <div style="display: flex; gap: 12px; justify-content: flex-end;">\n <button class="modal-cancel-btn" style="\n padding: 8px 16px;\n border: 1px solid #ddd;\n background: white;\n border-radius: 4px;\n cursor: pointer;\n ">キャンセル</button>\n <button class="modal-confirm-btn" style="\n padding: 8px 16px;\n border: none;\n background: #ff4d4f;\n color: white;\n border-radius: 4px;\n cursor: pointer;\n ">削除する</button>\n </div>\n ',e.appendChild(t),document.body.appendChild(e);const a=()=>{document.body.removeChild(e)},n=t.querySelector(".modal-cancel-btn"),i=t.querySelector(".modal-confirm-btn");n.addEventListener("click",a),i.addEventListener("click",async()=>{await this.clearLocalStorageData(),a()}),e.addEventListener("click",t=>{t.target===e&&a()});const s=e=>{"Escape"===e.key&&(a(),document.removeEventListener("keydown",s))};document.addEventListener("keydown",s)}async clearLocalStorageData(){if(!this._clearingLocalStorage){this._clearingLocalStorage=!0;try{this.settingsManager&&await this.clearSettings(),Object.keys(localStorage).forEach(e=>{e.startsWith("excelike_")&&localStorage.removeItem(e)}),this.closeFilter(),this.state.columnWidths={},this.state.visibleColumns={},this.state.pinnedColumns={},this.state.filters={},this.state.sortState={},this.state.rangeFilters={},this.state.dateRangeFilters={},this.state.fontSize="medium",this.state.cellPadding="standard",this.state.currentPage=1,this.state.openFilter=null,this.state.columnFilters&&(this.state.columnFilters={}),this.state.filteredData=[...this.state.data],this.state.sortedData=[...this.state.data],this.showTemporaryMessage("設定をクリアしました。ページが自動で再読み込みされます。","success"),setTimeout(()=>{window.location.reload()},500)}catch(e){try{this.showTemporaryMessage("設定クリア中にエラーが発生しました","error")}catch(e){}}finally{this._clearingLocalStorage=!1}}}applyDefaultVisualSettings(){if(!this.tableContainer)return;this.state.fontSize="medium",this.state.cellPadding="standard",this.applyCurrentStyling(),this.tableContainer.querySelectorAll("th").forEach(e=>{e.style.width="",e.style.minWidth="",e.style.maxWidth=""}),this.tableContainer.querySelectorAll("td").forEach(e=>{e.style.width="",e.style.minWidth="",e.style.maxWidth=""});const e=this.tableContainer.querySelector(".table");e&&(e.style.fontSize="",e.classList.remove("font-smallest","font-small","font-large","font-largest"),e.classList.remove("padding-wide","padding-narrow")),this.tableContainer.querySelectorAll(".pinned-left").forEach(e=>{e.classList.remove("pinned-left"),e.style.position="",e.style.left="",e.style.zIndex="",e.style.backgroundColor=""})}showTemporaryMessage(e,t="info"){const a=document.createElement("div");a.style.cssText=`\n position: fixed;\n top: 20px;\n right: 20px;\n padding: 12px 20px;\n border-radius: 4px;\n color: white;\n font-size: 14px;\n z-index: 10001;\n animation: slideIn 0.3s ease-out;\n background: ${"success"===t?"#52c41a":"error"===t?"#ff4d4f":"#1890ff"};\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n `;const n=document.createElement("style");n.textContent="\n @keyframes slideIn {\n from { transform: translateX(100%); opacity: 0; }\n to { transform: translateX(0); opacity: 1; }\n }\n @keyframes slideOut {\n from { transform: translateX(0); opacity: 1; }\n to { transform: translateX(100%); opacity: 0; }\n }\n ",document.head.appendChild(n),a.textContent=e,document.body.appendChild(a),setTimeout(()=>{a.style.animation="slideOut 0.3s ease-out",setTimeout(()=>{a.parentNode&&document.body.removeChild(a),n.parentNode&&document.head.removeChild(n)},300)},3e3)}destroy(){this.container.innerHTML="",this.closeFilter()}}"undefined"!=typeof module&&module.exports&&(module.exports=ExceLikeTable),"undefined"!=typeof window&&(window.ExceLikeTable=ExceLikeTable);